SlackのメッセージをX（旧Twitter）に自動投稿する方法（Lambda × API Gateway × SSM）

## 📝 本記事の前提と目的

* 本記事は筆者自身が学んだ知識をもとにテーマを設定し、構築・検証した結果を**アウトプットとしてまとめた記録**です。

* テーマ選定の背景として、現在所属しているスクールにおいてSlackを使用して進捗報告を行っており、その内容を自身のX（旧Twitter）においても活動報告として投稿できればと考えたことがきっかけです。

* 構成は**すべて無料プランの範囲内**で完結します。

  * Slack：無料アカウントで利用可能
  * X（旧Twitter）：無料開発者アカウントを利用
  * AWS：無料利用枠（Free Tier）を前提

* 有料プランや追加課金が必要な構成要素は一切使用していません。

* **本記事の内容は2025年5月31日時点の構成・画面・仕様に基づいて動作確認されています。**



## 🔷 全体の流れ（手順一覧）

この自動投稿システムは、Slackの特定チャンネルに投稿されたメッセージをトリガーに、AWS Lambdaを経由してX（旧Twitter）へ自動で投稿する構成です。

以下の6ステップで構成されます：

1. **Slackの環境構築**：アカウント、ワークスペース、チャンネルの作成
2. **X API設定**：認証情報の取得と設定
3. **Lambda構築**：モジュールのZIP化と関数作成
4. **API Gateway連携**：SlackとLambdaの接続設定
5. **Slackアプリ作成とBot設定**：イベントと権限付与
6. **動作確認**：Slackからの投稿がXに反映されることを確認

それでは各手順を順に詳しく解説していきます。


## 🧩 事前準備：Slackのワークスペースとチャンネル作成

### 0-1. Slackアカウントの作成

1. [Slack公式サイト](https://slack.com/) にアクセスし、「Slackを始める」からアカウントを作成します。
2. メールアドレスを登録し、認証コードを入力して進みます。

### 0-2. ワークスペースの作成

1. アカウント作成後、「新しいワークスペースを作成」画面が表示されるので、以下の項目を入力：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/76e35d1c-e05a-4143-9339-a17752c2f3c6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/19ca1e29-d867-4c4d-8894-5ffd9b0e37e6.png)

<br><br>
   * プロジェクトやチームの名前（例：MyDevWorkspace）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/4ebe9877-84ae-473b-a2db-1f568cecf727.png)

<br><br>
   * 名前（例：テスト）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/26ec9eb9-71b8-41e8-80a6-e22974e65cfe.png)

<br><br>
   * ※スキップしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/e8a0df54-1dca-47fd-a982-77a43b4a8094.png)
<br><br>
   * ※スキップしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/fa24ba0f-053c-4e28-900d-12cbdb0360cf.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/be21c114-1411-47af-92b0-fa6d73c2bd16.png)

### 0-3. チャンネルの作成

1. Slackワークスペースにログイン後、左側の「チャンネル」セクションで「＋チャネルを追加する」をクリック
2. 「次へ」ボタンをクリック
3. 任意のチャンネル名を設定（例：#auto-post）
可視性の設定はデフォルトのまま
3. 「作成」ボタンをクリック
4. 「後でする」ボタンをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/91f9a694-776e-493a-bc77-7d258e2f6df8.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/3e31c665-bf9d-4287-87bf-148411e58454.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/de93116b-13b2-429b-b34b-847727b10c02.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/79c2aaf2-388a-45a5-961d-8d356bec7d10.png)


※ 後ほどこのチャンネルにBotを招待し、投稿をトリガーに使用します。


## 🧩 手順①：X（旧Twitter）APIの設定

💡 **注意ポイント**

本構成では、認証設定画面で「OAuth 2.0（User Context）」の前提でトークンを取得し、Lambda からの投稿処理に使用します。

App Context や OAuth 1.0 を利用した場合、tweet.write 権限があっても投稿処理が失敗することがあります。

認証用のトークンは複数ありますが、今回の構成では**Bearer Tokenを使用**します。Lambda関数からの認証で必要です。

無料プランでもAPIキーは取得可能ですが、設定を誤ると認証エラーや投稿失敗の原因となるため、画面通りに設定を行ってください。

### 1-1. X Developer Portal にアクセス

1. [https://developer.x.com/](https://developer.x.com/) にログイン
2. 右上の 「Developer Portal」をクリック
2. 中央に表示されている「+ Create Project」クリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/d428a57a-0cd1-405c-8747-306dd5da87a3.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/c2efe7ee-3c71-4f88-a1db-66b1c3ef0065.png)

### 1-2. プロジェクトとアプリの設定

1. Project名（例：SlackXPoster）を入力
2. Use case（例：Exploring the API）を設定
3. Projekuct description：「Web App, Automated App or Bot」を入力
4. App名（例：SlackXPoster）を入力
5. 画面で表示される以下の内容をメモし、「App setting」をクリック
   * API KEY
   * API Key Secret
   * Bearer Token

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/67f622a0-5ab8-4e0b-9743-9e357589bb73.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/464677da-c1a2-4652-bf5e-6465ebf2c72f.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/7eee94a3-4c0a-4172-9712-bfec27dc4dfc.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/b331da2b-4642-4e2f-9f49-7cb7239e42fd.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/1ebcdcce-6438-4589-b86c-5fa450225c9d.png)

### 1-3. User authentication settings の設定

1. App permissions：「Read and write」を選択
   ※ 投稿操作に「Write」権限が必要です
2. Type of App：「Web App, Automated App or Bot」を選択
   ※Botとして扱うためこの設定にします
3. Callback URL：`https://example.com/callback`（ダミーでOK）を入力
※空欄では保存できないため適当なURLを設定（実際には使用しません）
4. Website URL：任意のURLを入力（例：[https://localhost.com])
※空欄でなければOK
5. 「Save」ボタンをクリックして設定を保存

⚠ 保存時にエラーが出た場合は、URL項目が空欄でないかを確認してください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/3e901191-8bd6-4e38-82f4-a57449ffadf6.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/f28ef72c-ecb8-45fc-a6d4-883486d18ce4.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/efdaaedf-00bd-47ad-b86b-30b2e8954367.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/fd751729-23ee-47ed-9932-41b8d7a1b2a1.png)
<br><br>
※以下は使用しません
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/3fce8ff6-879c-44c7-b0e8-25eef80f1741.png)
<br><br>
※以下は使用しません
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/d917ea0a-3fb4-4315-bf74-f544481cb510.png)

### 1-4. 公開鍵/秘密鍵の取得 の設定

1. ポータルの左メニューより「Projects ＆ Apps」を選択し、対象のアプリを選択
2. 中央のアプリ名の下にある「Keys and tokens」を選択
3. Access Token and Secretより「Regenerate」をクリック
画面で表示される以下の内容をメモ
   * Access Token
   * Access Token Secret

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/6381336a-c132-4341-a28d-1e8452c48571.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/285f64e7-e82e-42ca-959b-00f10ef7f1ca.png)


## 🧩 手順②：Lambdaとモジュールの準備（ローカル環境）

💡 **注意ポイント**

* `lambda_function.py` はZIPのルートディレクトリに配置する必要があります。
* モジュールのZIPを作る際、必ず依存ライブラリ（`tweepy`など）を同じディレクトリに展開してください。
* `pip install -t .` によってできるサブディレクトリをZIPに含め忘れると、Lambda実行時に `ModuleNotFoundError` になります。

### 2-1. tweepyを含むLambdaパッケージ（ZIP）の作成

※ Pythonの仮想環境は使用せず、カレントディレクトリに直接モジュールをインストールする方法で進めます。

```bash
# tweepy用の作業ディレクトリを作成します
mkdir tweepy_layer

# 作成したディレクトリに移動します
cd tweepy_layer

# tweepyライブラリを現在のディレクトリ（=ZIP化対象）にインストールします
# Lambdaで直接使用できるように仮想環境は使わずにローカルに展開します
pip install tweepy -t .
```
以下は実行例です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/e5b37750-e851-44bc-bed5-5618715b06d0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/c5eb1e45-bf88-4c4c-8cbd-7cc3e0606a47.png)

### 2-2. lambda\_function.py を同フォルダに保存
※意図的にコメントを多く入れています

```python
# -------------------------------
# 必要なライブラリを読み込みます
# -------------------------------
import json        # 文字列をPythonの辞書に変換するために使用
import boto3       # AWSのサービス（SSM）を操作するために使用
import tweepy      # X（旧Twitter）に投稿するためのライブラリ

# -------------------------------
# SSM（パラメータストア）から各種キーを安全に取得します
# -------------------------------
def get_param(name):
    ssm = boto3.client('ssm')
    return ssm.get_parameter(Name=name, WithDecryption=True)['Parameter']['Value']

# -------------------------------
# 認証情報をSSMから取得します（事前にAWS Systems Manager Parameter Storeに登録しておく必要があります）
# 各キーの役割は以下のとおりです：
# 
#   /slack-to-x/BEARER_TOKEN     → X APIのベアラートークン（読み取りなどに使用、投稿には不要な場合あり）
#   /slack-to-x/ACCESS_TOKEN     → 投稿するユーザーのアクセストークン（OAuth 1.0aで使用）
#   /slack-to-x/ACCESS_SECRET    → 上記トークンに対応する秘密鍵（署名に必要）
#   /slack-to-x/CONSUMER_KEY     → アプリの識別用ID（API Keyとも呼ばれる）
#   /slack-to-x/CONSUMER_SECRET  → アプリの秘密鍵（Consumer Keyとセットで使う）
#
# 投稿機能を使うには CONSUMER_KEY/SECRET + ACCESS_TOKEN/SECRET の4つが必要です。
# BEARER_TOKEN はOAuth 2.0の認証用トークンで、読み取りAPIなどで使われます。
# -------------------------------
BEARER_TOKEN    = get_param('/slack-to-x/BEARER_TOKEN')
ACCESS_TOKEN    = get_param('/slack-to-x/ACCESS_TOKEN')
ACCESS_SECRET   = get_param('/slack-to-x/ACCESS_SECRET')
CONSUMER_KEY    = get_param('/slack-to-x/CONSUMER_KEY')
CONSUMER_SECRET = get_param('/slack-to-x/CONSUMER_SECRET')

# -------------------------------
# tweepy（X APIクライアント）を初期化します
# -------------------------------
client = tweepy.Client(
    bearer_token=BEARER_TOKEN,
    consumer_key=CONSUMER_KEY,
    consumer_secret=CONSUMER_SECRET,
    access_token=ACCESS_TOKEN,
    access_token_secret=ACCESS_SECRET
)

# -------------------------------
# Lambdaのメイン処理です
# -------------------------------
def lambda_handler(event, context):
    print("📩 Slackイベントを受信しました:", json.dumps(event))

    # -------------------------------
    # Slackの再送処理（retry）に対応します
    # -------------------------------
    if "headers" in event:
        headers = event["headers"]
        retry_num = headers.get("X-Slack-Retry-Num")

        # 再送回数が3回以上なら処理をスキップ
        if retry_num is not None:
            try:
                if int(retry_num) >= 3:
                    print(f"🚫 リトライ上限（{retry_num}回）を超えたため処理しません")
                    return {
                        "statusCode": 200,
                        "body": json.dumps("Retry limit exceeded, skipping")
                    }
            except ValueError:
                print("⚠️ リトライ回数の形式が不正です")

        # 通常の再送検知ログ（1回目〜2回目）
        if "X-Slack-Retry-Num" in headers:
            print(f"🔁 Slackリトライ検知: retry_num = {retry_num}")

    try:
        # -------------------------------
        # JSON形式のリクエストボディを辞書に変換します
        # -------------------------------
        body = json.loads(event.get("body", "{}"))

        # -------------------------------
        # SlackからのURL検証リクエストに応答します
        # -------------------------------
        if body.get("type") == "url_verification":
            print("🔐 URL検証リクエストを受信しました")
            return {
                "statusCode": 200,
                "headers": {"Content-Type": "application/json"},
                "body": json.dumps({"challenge": body.get("challenge")})
            }

        # -------------------------------
        # Slackイベント本体（メッセージ内容など）を取り出します
        # -------------------------------
        event_data = body.get("event", {})

        # Bot（自分自身など）が投稿したメッセージは無視します（ループ防止）
        if event_data.get("bot_id"):
            print("🤖 Botによる投稿のため処理をスキップします")
            return {
                "statusCode": 200,
                "body": json.dumps("Bot message ignored")
            }

        # -------------------------------
        # 投稿内容（text）を取得し、空白を除去します
        # -------------------------------
        text = event_data.get("text", "").strip()

        # -------------------------------
        # textが空の場合、blocksの中からテキストを補完取得します
        # -------------------------------
        if not text:
            blocks = event_data.get("blocks", [])
            for block in blocks:
                if block.get("type") == "section":
                    text_candidate = block.get("text", {}).get("text", "").strip()
                    if text_candidate:
                        text = text_candidate
                        print(f"🧩 blocksからテキストを取得: {text}")
                        break

        # 内容が空の場合は投稿しません
        if not text:
            print("⚠️ メッセージが空のため処理をスキップします")
            return {
                "statusCode": 200,
                "body": json.dumps("No text to tweet")
            }

        # -------------------------------
        # Xに投稿するメッセージを作成し投稿します
        # -------------------------------
        tweet_text = f"{text}（自動投稿）"
        print(f"✍️ 投稿内容: {tweet_text}")
        client.create_tweet(text=tweet_text)
        print("✅ Xへの投稿が完了しました")

        return {
            "statusCode": 200,
            "body": json.dumps("Tweeted successfully")
        }

    # -------------------------------
    # 投稿制限（429エラー）の場合はSlackに成功応答を返し、再送を防ぎます
    # -------------------------------
    except tweepy.errors.TooManyRequests as e:
        print(f"❌ 投稿制限（429）に達しました: {str(e)}")
        return {
            "statusCode": 200,
            "body": json.dumps("Rate limit exceeded, skipping retry")
        }

    # -------------------------------
    # 他の予期しないエラーもSlackに成功と返し、再送されないようにします
    # -------------------------------
    except Exception as e:
        print(f"❌ 予期せぬエラーが発生しました: {str(e)}")
        return {
            "statusCode": 200,
            "body": json.dumps("Unexpected error occurred, retry skipped")
        }
```
### 2-3. ファイル構成の確認

以下のようなディレクトリ構成に最終的になっていれば準備完了です：

```
tweepy_layer/
├── lambda_function.py
├── tweepy/（インストールされたライブラリ）
├── requests/（依存ライブラリ）
└── その他の必要なライブラリ/
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/c9d7f719-cfc5-463b-9d22-212d3e448c40.png)

### 3-4. モジュールをZip形式でパッケージ化する
```powershell
# Windows環境でのZIPファイル作成コマンドです
# このコマンドはカレントディレクトリ配下のすべてのファイルとフォルダをZIPに圧縮します
# 作成されたZIPファイル（tweepy_lambda.zip）はLambdaへアップロードします
Compress-Archive -Path * -DestinationPath tweepy_lambda.zip
```
以下は実行例です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/610055d0-3b31-4e17-8362-2caa0f93fe0b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/5582dda1-52d4-4c4c-b390-c5f36110639a.png)


## 🧩 手順③：AWS Lambda関数の作成と環境変数の設定

💡 **注意ポイント**

* Lambda作成時に指定するロールには「SSM読み取り権限」が含まれている必要があります。
* コードアップロード後は「保存」を忘れずに。
* SSMのパラメータ名とコード内のキー名（パス）は完全一致していないと読み取りに失敗します。
* Lambda関数の**タイムアウト（実行時間制限）**はデフォルトでは3秒と短いため、10〜15秒に延長しておくのが安全です。

### 3-1. Lambda関数の作成

1. AWSマネジメントコンソールにログインし、サービスから「Lambda」を選択
2. 「関数の作成」をクリック
3. 以下の内容で入力：

   * 関数名：SlackToXPoster
   * ランタイム：Python 3.11
   * 実行ロール：基本的なLambda権限付きの新規ロールを作成
4. \[関数の作成] をクリックして完了

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/f6d46fbb-28b2-4784-975e-0b802e946c06.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/6bba6e41-c868-437c-9b99-d24e3db7427b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/97dc270d-fa0b-4917-bcb1-7d1915512da1.png)

### 3-2. ZIPファイルのアップロード

1. 「コード」タブを開き、「.zipファイルをアップロード」を選択
2. 作成済みの `tweepy_lambda.zip` をアップロード
3. 「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/1f970115-0344-4e8d-8b60-8bf4783be98f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/929db627-1495-445b-a158-907c7091884f.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/248cad2d-1ec7-4be1-b0f8-756d9577dee9.png)

#### ✅ LambdaにSSM読み取り権限を付与

1. Lambda関数 → \[設定] → \[編集] を開く
2. 既存のロールにて「ロールの表示」を開く
3. IAMロールを開き、以下の通り許可の追加
4. 「ポリシーのアタッチ」 → \[AmazonSSMReadOnlyAccess] の許可を追加

このようにすれば、Lambda環境変数に直接キーを保存する必要がなくなり、安全性が大幅に向上します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/e6e12cfe-72dd-4cca-8638-177e817ef3be.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/fab5d3f0-5d7b-4ac1-839c-148c5c04a20e.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/8c1e5d05-af10-4269-b63e-b8d9a2d43c30.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/f160eb84-3216-4fb3-a17b-a88b2db3e478.png)

### 3-3. Lambdaのタイムアウト設定変更

1. Lambda関数詳細ページで「設定」タブをクリック
2. 一般設定セクションにある「タイムアウト」の右側の編集ボタンをクリック
3. 「15秒」に変更して「保存」ボタンをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/07bdaef9-0dd8-489f-8861-09f4433b9afa.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/e7eaa73c-2614-4f65-8667-e57f87a049a1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/e7b22d14-fcc1-4265-885c-c96269858400.png)


### 3-4. パラメータストアによる秘匿情報の安全な管理（必須）

**環境変数ではなく、AWS Systems Manager Parameter Store に各種APIキーを安全に保存し、Lambda関数内で取得する方法を推奨します。**

#### ✅ SSMパラメータの登録手順

1. AWSマネジメントコンソール → Systems Manager → 「パラメータストア」→「パラメータの作成」
2. 以下の項目を入力（1つずつ作成）：

   * 名前: `/slack-to-x/BEARER_TOKEN`（任意のパス構成でOK）
   * タイプ: 「SecureString」※安全な文字列
   * 値: 実際のトークンやキーを入力　※事前にxにて取得した値を設定
   * KMSキーはデフォルトのままでOK
3. 同様に以下の5つを作成：

   * `/slack-to-x/BEARER_TOKEN`　※Bearer Token
   * `/slack-to-x/ACCESS_TOKEN`　※Access Token
   * `/slack-to-x/ACCESS_SECRET`　※Access Token Secret 
   * `/slack-to-x/CONSUMER_KEY`　　※API Key
   * `/slack-to-x/CONSUMER_SECRET`　※API Key Secret

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/51c1e930-fd39-40c5-a17e-8d2f4a6e183e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/af096016-7747-42cc-bb73-f083ca8b6274.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/edc7a697-675c-4242-a520-f9a66c45dc4e.png)
※他同様
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/55984297-0028-4747-925c-0fc0041445e0.png)



## 🧩 手順④：API Gatewayの作成とSlackとの接続

💡 **注意ポイント**

* API Gatewayの通信方式は「HTTP API」を選択してください。REST APIではSlackとの統合が正常に動作しない可能性があります。
* SlackからのリクエストはHTTPSである必要があるため、API GatewayエンドポイントのURLスキーム（https\://）を必ず確認してください。
* API Gatewayで統合対象のLambda関数を誤るとSlackからリクエストが届かなくなります。
* SlackのEvent Subscriptionsで「Verified」と表示されない場合、Lambdaの実行権限や戻り値の形式（JSON）を見直してください。

### 4-1. HTTP APIの作成

1. AWSマネジメントコンソールで「API Gateway」を開く
2. 「APIを作成」→「HTTP API」を選択
3. 　API名「SlackToXPoster」
4. 「統合」→「Lambda関数」を選択
4. 　Lambda関数にて「SlackToXPoster」 関数を指定
5. \[デプロイステージ] は `default` でOK → 作成完了
6. 発行された「エンドポイントURL」を控える

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/d47a25bd-fd79-4fd8-8040-b7250c6eb95c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/cf9dd49f-e66e-4535-90ee-0d0273459df6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/4b354abc-d502-48cf-8e18-ae586c5e3d71.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/12509710-f6fa-4984-9031-20aa6f5ef120.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/37e3a162-fdb8-4763-8a1c-fdf63b4df072.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/438aa5b2-27cc-4bdb-8592-5646008e0ec8.png)

Lambdaの「+トリガーを追加」からでも作成できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/d97ae24f-ea05-4cb6-9eb6-2617b5cbfb4d.png)


## 🧩 手順⑤：Slack Appの作成と設定

💡 **注意ポイント**

* Event Subscriptionsの「Request URL」には、必ず後で発行される「API GatewayのエンドポイントURL」を正確に入力してください。未設定または誤りがあるとSlackの検証が通らず「Verified」になりません。
* `chat:write` スコープがないとBotが投稿できません。
* Botをチャンネルに招待しないとイベントが受け取れません。必ず `/invite @Bot名` を実行してください。

### 5-1. Slack Appを作成

1. [Slack API](https://api.slack.com/apps) にアクセス
2. \[Create New App] → \[From scratch] をクリック
3. App Nameに「SlackToXBot」と入力
4. 作成したワークスペースを選択 → \[Create App]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/9330f2f0-066f-4f25-99fa-db4a9aa5a71e.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/509ea67a-6dae-4fe5-b238-a34bde39b777.png)
<br><br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/5172bec2-c1da-45c9-8cc8-0bd27825fcf5.png)

### 5-2. Bot権限の設定（OAuth & Permissions）

1. 左メニューから \[OAuth & Permissions] を選択
2. 下へスクロールし「Scopes」の「Bot Token Scopes」を追加：

   * `chat:write`：投稿権限
   * `channels:read`：チャンネル一覧取得
   * `channels:history`：チャンネルメッセージ取得
   * `im:history`：DMメッセージ取得


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/325ef6a1-310e-4aa3-8bda-441e65c158df.png)
<br><br>

設定前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/00724fb9-0d2d-4d52-acb2-e803cfeedb7e.png)

設定後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/663f76b4-f53f-4746-8d1c-a96deef8e6ad.png)

Request URLがないと設定できないので一旦スキップ
### 5-3. イベントサブスクリプション設定

1. 左メニュー「Event Subscriptions」
2. \[Enable Events] を ON
3. 「Request URL」欄は後でAPI GatewayのURLを設定（仮で空欄OK）
4. 下の \[Subscribe to bot events] → 「Add Bot User Event」ボタン

   * `message.channels`
   * `message.im`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/db44c3e2-e9b9-4a0b-a982-820b4e07db32.png)
<br><br>

設定前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/a937cb31-c64d-42f6-bde0-4010eff76fca.png)

設定後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/fb7e9665-8f5c-478e-9f49-131b4d97d334.png)


### 5-4. アプリをワークスペースにインストール

1. 左メニュー \[Install App] → 「Install to Workspace」
2. 「許可する」をクリック
3. 発行されたBot Token（例：`xoxb-...`）をメモ（Lambdaの環境変数で使う）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/4445986f-a4aa-4c91-a1f8-319b98d09df7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/d3d7a61b-7ccc-40b0-a731-5bd44220f228.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/f12bf0ac-6d87-4a23-b8a5-d18c6407d85c.png)

### 5-5. Botをチャンネルに招待

1. Slackで `/invite @SlackToXBot` を実行
2. 招待先は先ほど作成した #auto-post チャンネル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/3af2503a-5e35-4f62-991c-fcfa272b1684.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/b1daa7be-e167-4c4e-b263-4029d29a8839.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/6a1cd051-ee14-4c81-b7bc-fb71137fd36d.png)

## 🧩 手順⑥：動作確認

💡 **注意ポイント**

* CloudWatchのログは「/aws/lambda/関数名」で確認できます。
* Slackでの投稿がXに反映されない場合、Lambdaのログに `KeyError`, `ModuleNotFoundError`, `Unauthorized` などの記録があるか確認してください。

1. SlackでBotを追加したチャンネルに任意のメッセージを投稿
2. 数秒後、X（旧Twitter）にその内容が投稿されていれば成功
3. 投稿されない場合はCloudWatchログを確認し、エラーログをもとにデバッグ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4081366/58e8b3f4-8af8-4830-b10c-a217538a8ec4.png)



以上でSlack → X自動投稿システムの全構築が完了です。


## ✍️ 感想と振り返り

今回の構成の中で、特に苦労したのが「X（旧Twitter）APIとの接続部分」です。

無料プランの制限や、v2/v1.1エンドポイントの混在により、

* 必要な投稿エンドポイントにアクセスできない
* 認証方式（OAuth2とBearer Tokenの違い）で混乱
* APIレスポンスのエラー内容が分かりづらく原因特定に時間がかかった

など、想定以上に調査と試行錯誤が必要でした。

実装上の処理は単純であっても、認証とAPI仕様の理解が伴っていないと「動かない原因が何か」にたどり着けず、理解が進んでいないとハードルとなる印象でした。

## 🚀 今後の展望

本記事で構築したSlack → X（旧Twitter）自動投稿システムは、基本的な連携機能を中心に構成していますが、今後考えられる改善・拡張の方向性として以下のような内容があります。

✔️ Xの文字数制限への対応

* Xの投稿は全角140文字という制限があります。
* 将来的にはLambda内でメッセージの文字数チェックを行い、
  * 超過時に自動でカット or …（省略）で終える
  * または分割投稿する仕組みやAIなどでの字数制限内の要約ができると理想的です。