コンテンツにスキップ

Webhook

RolChat は、署名付きの HTTP POST リクエストとしてイベントを自社システムに配信します。このページではプラグインの Webhook を説明します。オートメーションルールが送る Webhook は、再試行、待機時間、シークレットのローテーションが異なります(自動化の Webhook を参照)。逆方向、つまり自社システム、プラグイン、プロバイダーが RolChat に送信する場合も、同じルールが適用されます。配信を処理する前に、次の順序ですべての配信を検証してください。

POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: RolChat-Webhook/1.0
X-RolChat-Timestamp: 1713615825
X-RolChat-Signature: sha256=a1b2c3…
X-RolChat-Nonce: nonce_01HKQN9…
X-RolChat-Event-Id: evt_01HKQN9…
{ "id": "evt_01HKQN9…", "type": "conversation.created", "occurred_at": "…", "data": { … } }
ヘッダー 意味
X-RolChat-Timestamp 送信時の Unix 秒。署名対象のメッセージの一部です。
X-RolChat-Signature sha256=<hex>。プレフィックスはアルゴリズム名で、将来変更できるようになっています。
X-RolChat-Nonce この HTTP リクエストに固有の ULID。再試行では新しいノンスが使われます。
X-RolChat-Event-Id イベント自体の ID。再試行をまたいで変わらないため、重複排除に使います。

送信する配信の宛先は HTTPS のエンドポイントのみです。HTTP の URL は、購読の作成時に拒否されます。

署名対象のメッセージは、タイムスタンプ、ドット1文字、そして**未加工のリクエスト本文(バイト単位でそのまま)**です。

canonical = "<timestamp>.<body>"
signature = hex( HMAC-SHA256( secret, canonical ) )

受け取ったバイト列に署名してください。解析した JSON を再シリアル化したものではありません。空白やキーの順序が変わると署名は一致しなくなります。受け付けるアルゴリズムは SHA-256 のみです。

次の5つのチェックを順番に行ってください。Python、Node.js、Go、Ruby、PHP のサンプルは検証サンプルのページにあります。

  1. 有効期間。 |now − X-RolChat-Timestamp| > 300 秒であれば拒否します(400)。5分あれば時刻のずれを許容でき、それを過ぎると傍受されたリクエストは役に立たなくなります。サーバーは NTP で時刻を同期してください。
  2. 署名。 期待される値を計算し、定数時間の比較(hmac.compare_digest、crypto.timingSafeEqual、hash_equals)で比較します。一致しなければ 401 で拒否します。通常の文字列比較はタイミング情報が漏れるため使えません。
  3. ノンス。 アトミックな「存在しない場合のみ設定」(SET NX EX 600)を使い、X-RolChat-Nonce を10分間の TTL で保存します。すでに存在する場合は 409 で拒否します。そのリクエストはリプレイです。ノンスの保存先が停止している場合は、RolChat が再試行するよう 503 を返して安全側に倒してください。決して「受け入れ」にフォールスルーしないでください。
  4. 本文の解析はこの段階で初めて行います。
  5. イベント ID で重複を排除します。 再試行には新しいノンスが付きますが、X-RolChat-Event-Id は同じです。過去7日以内にその ID を処理していれば、再処理せずに 200 を返します。

配信を安全に受け付けたら、すぐに 2xx を返し、処理は非同期で行ってください。RolChat はレスポンスを15秒間待ちます。

  • Webhook のシークレットは、base64url でエンコードされた64バイトのランダム値(約86文字)です。購読またはプラグインの作成時に生成され、一度だけ表示されます。
  • 自社のボールトや環境変数に保存してください。RolChat は暗号化したコピーのみを保持します。
  • 90日後のローテーションを推奨します。製品はリマインダーを表示しますが、ローテーションしなかったことを理由に購読を無効にすることはありません。
  • ローテーションでは重複期間を使います。POST /api/v1/plugins/{id}/webhook-secret/rotate の後、7日間は古いシークレットと新しいシークレットの両方で検証が通り、新しい配信は新しいシークレットで署名され、古いシークレットは期間の終了後に削除されます。ローテーションした瞬間に何かが壊れることはありません。
  • シークレットが漏洩した場合、POST /api/v1/plugins/{id}/webhook-secret/revoke でそのシークレットを即座に無効化し、新しいシークレットを発行し、アカウントの管理者にメールを送り、監査記録を残します。

2xx 以外のレスポンスまたはネットワーク障害があると、再試行が予約されます。

試行 前回からの間隔
1 すぐに
2 15秒
3 1分
4 5分
5 30分
6 2時間
7 8時間
8 24時間

408、429、5xx、ネットワークエラーは再試行されます。400、401、403、404、410 は再試行されず、配信はそのままデッドレターキューに入ります。拒否された署名や存在しないエンドポイントに再試行しても意味がないためです。8回失敗した配信もデッドレターになります。

デッドレターは30日間保持され、設定 → プラグイン → {プラグイン} → Webhook ログ → DLQ で確認できます。管理者はそこから再送できます(POST /api/v1/plugins/{id}/webhook-dlq/{entry_id}/replay)。

  • 送信する本文: 既定で 1 MB(プラグインごとに設定可能)。RolChat への受信本文: 5 MB。それを超えると 413 になります。
  • プラグインは allowed_source_ips を宣言でき、RolChat は検証の前にその許可リストを適用します。
  • 追加の妥当性チェックとして、RolChat-Webhook/1.0 ユーザーエージェントを確認できます。これはセキュリティ対策ではありません。

プロバイダーは、独自の署名方式(Twilio、Telnyx、Bandwidth、WhatsApp の場合は Meta、Stripe)を使って /api/v1/webhooks/{category}/{adapter} で RolChat に送信します。各アダプターはプロバイダーの署名を検証し、ペイロードを正規化して、RolChat の署名付きで内部のイベントバスに再発行します。自社システムからも、上で説明した X-RolChat-* ヘッダーを使い、そのエンドポイントを所有するプラグインのシークレットで署名して RolChat に送信できます。

オートメーションモジュールの HTTP リクエストを送信アクションには、独自の配信ルールがあります。

  • アカウントごとにシークレット1つ。 オートメーション画面で表示またはローテーションできます(オーナーと管理者)。ローテーションは即座に有効になります。重複期間はなく、古いシークレットはすぐに検証できなくなるため、先に受信側を更新するか、短い空白期間を許容してください。
  • 待機時間: RolChat は応答を10秒間待ちます。
  • 再試行: 合計で最大6回試行します。2回目の試行の前の待ち時間は30秒、その後は約2分、8分、32分、2時間8分で、それぞれ最大20 % ばらつきます。1回の待ち時間が6時間を超えることはありません。
  • 再試行の対象: ネットワークエラー、タイムアウト、5xx、408、425、429。それ以外の 4xx の応答、ブロックされたアドレス、大きすぎる本文は再試行されません。その配信は、最後の試行が失敗した場合と同様に、直接デッドレターキューに入ります。
  • ヘッダー: X-RolChat-Signature、X-RolChat-Signature-V2、X-RolChat-Timestamp、X-RolChat-Delivery-Id、X-RolChat-Attempt。

POST /api/v1/webhooks/test-signature は、呼び出し元のプラグインのエンドポイントに合成の署名付きイベントを送信します。実際のトラフィックが届く前に、実装をエンドツーエンドで検証できます。

  • 未加工のバイト列ではなく、解析して再エンコードした JSON で検証する。
  • 署名の比較に == を使う。
  • イベント ID ではなくノンスで重複を排除する(ノンスは再試行のたびに新しくなります)。
  • 時刻が5分以上ずれている。
  • 応答する前に時間のかかる処理を行い、15秒でタイムアウトして同じイベントを再び受け取る。