# Threads Webhook 通知：キーワードと人気投稿をリアルタイムで受信

Webhook を使えば、API を何度もポーリングする必要はありません。ルールを設定しておくと、条件に合う投稿が Threads に現れたときに、Throk が投稿データを指定の URL へ `POST` します。よくある使い方は次のとおりです。

- ブランドや商品が言及され、盛り上がり始めたときに、Slack、LINE、社内システムへ通知する
- 話題の中で、いいね数、返信数、閲覧数がしきい値を超えた投稿をモニタリングする
- 今まさにバズっている投稿を、データベースや CRM へ自動で取り込む

:::note[必要なプラン]
Webhook を使うには **API Standard 以上**のプランが必要です。詳しくは [API プラン](/ja/api/credits/#api-プラン)をご覧ください。
:::

## 仕組み

Webhook は 2 つの要素で構成されます。

- **エンドポイント（Endpoint）**：通知を受け取る URL。例：`https://example.com/throk-webhook`。エンドポイントごとに専用の署名シークレットがあります。
- **ルール（Rule）**：どの投稿を送信するかを決める条件。各ルールは 1 つのエンドポイントに属し、1 つのエンドポイントに複数のルールを設定できます。

Throk は **3 分**ごとにすべてのルールをチェックします。条件に合う投稿は、**投稿 1 件につき 1 イベント**としてエンドポイントに送信されます。同じ投稿が同じルールで送信されるのは 1 回だけです。

## ルールの種類

### キーワードルール

投稿にキーワードが含まれ、**かつ**設定したしきい値に達したときに送信します。

- キーワードは 1 つのルールにつき 1 つです。照合方法は[キーワード検索](/ja/api/guides/search-rules/)と同じで、中国語は文字単位、英語は単語単位で照合します。
- チェック対象は、**直近 24 時間以内に投稿された**ものです。
- しきい値は少なくとも 1 つ設定してください。最低値は、いいね数 ≥ 10、返信数 ≥ 5、閲覧数 ≥ 20,000 です。

### 人気しきい値ルール

キーワードを設定せず、指定した期間内に投稿され、しきい値に達した投稿を送信します。今まさにバズっているコンテンツを見つけるのに向いています。

- チェック対象は、**過去 1〜72 時間以内**に投稿されたものです（`lookbackHours` で設定）。
- しきい値は少なくとも 1 つ設定してください。最低値は、いいね数 ≥ 500、返信数 ≥ 100、閲覧数 ≥ 20,000 です。

### 2 種類のルールに共通する点

- **同じルール内のしきい値は「かつ」の関係です。** 例：いいね数 ≥ 100 **かつ**返信数 ≥ 10。
- **異なるルールは互いに影響しません。** 1 件の投稿が 2 つのルールに合致した場合は、2 つのイベントが送信されます。
- **地域で絞り込めます。** `chinese`（中国語圏すべて、デフォルト）、`tw`、`hk`、`cn`、`japanese`、`korean` を指定でき、定義は[地域と言語](/ja/api/guides/regions/)と同じです。
- **返信も対象です。** 返信がルールに合致して送信されることもあります。イベントの `is_reply` フィールドで区別できます。
- **閲覧数のしきい値は遅れて発火します。** 投稿の閲覧数は通常、投稿から数時間後にデータが反映されます。そのため、閲覧数のしきい値を設定したルールは、いいね数や返信数だけを見るルールより送信が遅れます。

## 開発者プラットフォームで設定する

1. [開発者プラットフォーム](https://developer.throk.ai/dashboard/webhooks)にログインし、**エンドポイントとルール**を開きます。
2. **エンドポイントを追加**を押し、URL を入力して**作成**を押します。
3. エンドポイントの下にある **+ ルールを追加** を押し、**キーワード**または**人気しきい値**を選びます。言語としきい値を設定し（人気しきい値の場合は「過去…時間」も設定します）、**追加**を押します。
4. エンドポイントの**テスト**を押して、サーバーがイベントを受信できることを確認します。テストイベントではクレジットを消費しません。
5. エンドポイントを展開すると **Signing Secret** が表示されます。これを使って[署名を検証](#署名の検証)します。

![開発者プラットフォームの Webhooks ページ：エンドポイントにキーワードルールを追加し、言語と、いいね数・返信数・閲覧数のしきい値を設定する](../../../../assets/screenshots/api/webhook-rule.jpg)

送信したすべてのイベントと、受信側のレスポンスステータスは**送信履歴**ページで確認できます。失敗したイベントは再送信できます。

## API で設定する

エンドポイントとルールは API でも管理できます。どのリクエストにも API キーが必要です。

| メソッドとパス | 用途 |
| --- | --- |
| `GET /webhook/endpoints` | エンドポイント、署名シークレット（`secret`）、ルールの一覧を取得 |
| `POST /webhook/endpoints` | エンドポイントを作成 |
| `DELETE /webhook/endpoints/{endpoint_id}` | エンドポイントを削除（配下のすべてのルールも削除） |
| `POST /webhook/endpoints/{endpoint_id}/rules` | エンドポイントにルールを作成 |
| `DELETE /webhook/rules/{rule_id}` | ルールを削除 |
| `POST /webhook/endpoints/{endpoint_id}/test` | テストイベントを 1 件送信（無料） |

エンドポイントを作成する：

```bash
curl -X POST 'https://premium-api.throk.ai/webhook/endpoints' \
  -H 'Authorization: Bearer sk-throk-...' \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://example.com/throk-webhook"}'
```

キーワードルールを作成する（いいね数 ≥ 100 の台湾の投稿）：

```bash
curl -X POST 'https://premium-api.throk.ai/webhook/endpoints/{endpoint_id}/rules' \
  -H 'Authorization: Bearer sk-throk-...' \
  -H 'Content-Type: application/json' \
  -d '{"keyword": "珍珠奶茶", "likeCount": 100, "language": "tw"}'
```

人気しきい値ルールを作成する（過去 6 時間以内、いいね数 ≥ 1,000 の投稿）：

```bash
curl -X POST 'https://premium-api.throk.ai/webhook/endpoints/{endpoint_id}/rules' \
  -H 'Authorization: Bearer sk-throk-...' \
  -H 'Content-Type: application/json' \
  -d '{"likeCount": 1000, "lookbackHours": 6}'
```

ルールのフィールド：

| フィールド | 説明 |
| --- | --- |
| `keyword` | キーワード。空欄にすると人気しきい値ルールになります |
| `likeCount`、`replyCount`、`viewCount` | いいね数、返信数、閲覧数のしきい値。少なくとも 1 つ指定します |
| `lookbackHours` | 過去何時間以内に投稿されたものをチェックするか。1〜72。人気しきい値ルールでは必須です |
| `language` | 地域。デフォルトは `chinese` |

ルールは変更できません。調整したい場合は、削除してから作り直してください（開発者プラットフォームでは直接編集できます）。

## 送信データの形式

各イベントは `POST` でエンドポイントに送信され、本文は JSON です。

```json
{
  "post": {
    "url": "https://threads.com/post/DXZoK1VlEvT",
    "code": "DXZoK1VlEvT",
    "caption": "串文內容……",
    "is_reply": false,
    "like_count": 1700,
    "reply_count": 293,
    "repost_count": 40,
    "reshare_count": 180,
    "impression_count": 254300,
    "post_timestamp": 1776760127,
    "media_url": ["https://media01.throk.ai/media/..."]
  },
  "user": {
    "username": "throk.ai",
    "full_name": "Throk｜定義 Threads 數據新標準",
    "profile_picture": "https://media01.throk.ai/profile/77928681697.jpg"
  }
}
```

- 数値は**送信時点**のスナップショットです。その後投稿の数値が伸びても、更新は送信されません。
- `repost_count` にはリポストと引用が含まれます。`impression_count` は、閲覧数のデータがまだない場合は `null` になります。
- イベントには**ルールの情報は含まれません**。どのルールで送信されたかを区別したい場合は、ルールごとに別のエンドポイントを作成してください。例：URL にパラメータを付けて `https://example.com/throk-webhook?rule=bubble-tea` とする。

## 署名の検証

すべてのイベントには署名が付いています。これにより、リクエストが確かに Throk から送られたもので、内容が改ざんされていないことを確認できます。

- 署名のシークレットは、エンドポイントの **Signing Secret** です（開発者プラットフォームでエンドポイントを展開すると表示されます。`GET /webhook/endpoints` のレスポンスの `secret` フィールドでも確認できます）。
- 署名はリクエストヘッダー `X-Throk-Signature` に含まれ、形式は `t=<UNIX タイムスタンプ>,v1=<署名>` です。
- 検証方法：Signing Secret をキーとして `<タイムスタンプ>,<生のリクエストボディ>` の HMAC-SHA256（16 進数）を計算し、`v1` と照合します。あわせて、リプレイ攻撃を防ぐため、タイムスタンプが数分以内であることも確認してください。

Node.js の例：

```js
import crypto from 'node:crypto';

// rawBody: 生のリクエストボディ（文字列）、header: X-Throk-Signature の値
function verify(rawBody, header, secret) {
  const fields = header.split(',').map((p) => p.split('='));
  const t = Number(fields.find(([k]) => k === 't')?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 分以内のリクエストのみ受け付ける
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(`${t},${rawBody}`).digest('hex'),
  );
  // シークレットの切り替え中は v1 が複数含まれることがあるので、いずれか 1 つと一致すれば OK
  return fields
    .filter(([k]) => k === 'v1')
    .some(([, sig]) => {
      const got = Buffer.from(sig);
      return got.length === expected.length && crypto.timingSafeEqual(got, expected);
    });
}
```

署名は**生のリクエストボディ**（raw body）で計算してください。いったん JSON としてパースしてから文字列に戻したものは使わないでください。

## 受信側の注意点

- できるだけ早く **HTTP 2xx** を返してください。時間のかかる処理はバックグラウンドで実行してください。
- 2xx を返さなかったイベントは**指数バックオフ**で自動的に再試行されます（最大 **10 回**）。開発者プラットフォームの**送信履歴**から手動で再送信することもできます。
- 1 つのエンドポイントが受信するイベントは **60 秒あたり最大 1,000 件**で、超えた分は後から送信されます。
- 本番のイベントには `X-Convoy-Idempotency-Key` ヘッダーが付き（テストイベントには付きません）、同じイベントの再試行では値が変わりません。このヘッダー（または投稿の `code`）で重複を排除し、二重処理を防いでください。

## 課金

- イベントを 1 件送信するごとに **20 クレジット**を消費します。クレジットは API の呼び出しと同じアカウントのものを共有します。同じ投稿が同じルールで課金されるのは 1 回だけです。課金はイベントの**送信時点**で発生し、受信側の応答結果には左右されません。エンドポイントが正常に受信できる状態にしておいてください。
- エンドポイントとルールの作成・削除、テストイベントでは**クレジットを消費しません**。
- クレジットが不足すると、イベントの送信は一時停止されます。クレジットを補充すると、まだチェック対象期間内にある投稿は次回のチェックで送信されます。
- Webhook を含まないプランにダウングレードした場合、既存のルールは残りますが、発火しなくなります。
- Webhook の消費は **Credits 使用履歴**に表示され、API の種類は `webhook` です。

## 送信を停止する

Webhook には一時停止のスイッチはありません。送信を止めるには、ルールまたはエンドポイントを削除してください。エンドポイントを削除すると、配下のすべてのルールも削除されます。