AI API リセラー向けパートナー API Webhook の構築: 署名付きイベント、永続的な配信、安全なリプレイ
コミットされたイベントの生成、テナントセーフなファンアウト、署名、再試行、リプレイ、SSRF 保護、スキーマの進化、および調整をカバーする、信頼性の高いパートナー API Webhook の実用的なアーキテクチャ。
パートナー API にポーリングすると、厄介な制御ループが作成されます。重要なアクションが遅れて到着し、変更されていないリソースが繰り返し取得され、同時更新が間違った順序で行われる可能性があります。アウトバウンド Webhook はその遅延を軽減しますが、これはリクエスト ハンドラーに追加された HTTP コールバックではなく耐久性のある通知システムとして構築されている場合に限ります。
重要なルールは単純です。Webhook は、すでにコミットされた状態遷移を報告します。移行を作成したり、元帳を置き換えたり、信頼できる記録になったりするものではありません。この区別は、イベントによって顧客のアクセスが凍結されたり、クレジット処理がトリガーされたり、請求書に影響を与えたりする可能性がある場合に重要になります。
最初に配送契約を定義します
既知の事実: 一般的な Webhook 仕様と大規模な Webhook パブリッシャーは少なくとも 1 回の配信を前提としています。再試行すると重複が生成される可能性があり、独立した配信が順番どおりに到着しない可能性があります。
推奨事項: エンドポイント管理を公開する前に次のコントラクトを公開します API:
- すべてのビジネス イベントには、安定した不変のイベント ID があります。
- 同じイベント ID は自動再試行と手動再試行の間で保持されます。
- 配信は少なくとも 1 回です。 1 回限りの配信は約束されていません。
- 順序はエンドポイントまたはイベント タイプ間で保証されません。
- 応答が成功したということは、受信側がイベントを永続的に受け入れたことを意味しますが、必ずしもすべてのダウンストリーム作業が完了したわけではありません。
- Webhook は通知です。パートナー API が読み取り、元帳のエクスポートは引き続き権限を持ちます。
アクセス制御と財務イベントには迅速な通知と信頼できる回復パスの両方が必要であるため、この契約は AI API 再販業者プラットフォームに適合します。
絞り込んだイベント カタログから始めます
すべてのデータベース更新を公開イベントにしないでください。パートナーが操作して正確に定義できるトランジションを公開します。
| イベント | いつ発行するか | 一般的なパートナーのアクション |
|---|---|---|
customer.created | 顧客作成コミット | 販売代理店で顧客をプロビジョニングするportal |
customer.frozen | 有効なアクセス状態が凍結に変更 | ローカル サインインを無効にするか、サポートに通知 |
api_key.created | 主要なメタデータのコミット | 顧客の更新キーの在庫 |
api_key.revoked | 取り消しが有効になります | キーへの作業の割り当てを停止します |
balance.low | 利用可能な残高が設定された下方しきい値を超えています | リクエスト補充するか顧客に警告する |
budget.threshold_crossed | 確定した支出がポリシーのしきい値を超える | ローカル ワークフローまたは承認ルールを適用する |
usage.finalized | 使用量が暫定から暫定に移行完了 | レポートまたは下流の請求を更新 |
credit.adjustment_posted | 元帳調整コミット | 表示された残高を更新 |
しきい値イベントには明示的な交差ルールが必要です。たとえば、後続の使用状況レコードごとに発行するのではなく、以前の値が 80 パーセント未満で、新しい値が 80 パーセント以上になったときに発行します。反転によってしきい値を再度設定できるかどうか、および調整により新しいクロスを生成できるかどうかを定義します。
推奨事項: 完全な API キー、プロバイダーの資格情報、生のプロンプト、または詳細な不正行為の証拠をペイロードに含めることは避けてください。リソース識別子と最小限の状態のスナップショットを優先します。受信者はパートナー API を使用して、機密または急速に変化する詳細を取得できます。
コミットされた状態からイベントを作成する
リクエスト ハンドラーから直接発行すると、2 つの失敗ウィンドウが作成されます。データベース トランザクションがコミットされる前にコールバックがエスケープされる場合や、コールバックを送信する前にプロセスが失敗している間にトランザクションがコミットされる場合があります。トランザクション送信ボックスは両方のウィンドウを閉じます。
トランザクション送信トレイ シーケンス
- コントロール プレーンまたは台帳トランザクションを開始します。
- 権限のある状態の変更を適用します。
- 同じトランザクション内の送信トレイ テーブルに不変イベントを挿入します。
- 一度コミットします。
- パブリッシャーの従業員がコミット済みの送信トレイ行を要求できるようにします。
- イベントのパートナー スコープとサブスクリプション フィルターを使用して、対象となるエンドポイントを解決します。
- イベントとエンドポイントのペアごとに 1 つの配信レコードを作成します。
(event_id, endpoint_id) の一意の制約により、ワーカーの再試行時にファンアウトが重複するのを防ぎます。送信ボックスのパブリケーション ステータスは、複数のワーカーが安全に連携できるように、compare-and-set セマンティクスまたは行ロックを使用して更新する必要があります。
outbox_event(
event_id, partner_id, type, subject_id,
occurred_at, schema_version, payload, correlation_id,
published_at
)
delivery(
delivery_id, event_id, endpoint_id, state,
attempt_count, next_attempt_at, last_status,
last_latency_ms, last_error_category
)
送信トレイにより、データベースの書き込み、クリーンアップ作業、バックログの監視が追加されます。通知が経済的またはアクセス制御に影響を与える場合、これらのコストは正当化されます。ベストエフォート型コールバックは、使い捨て製品テレメトリでは引き続き許容される可能性がありますが、パートナー API Webhook システムの信頼性に関する主張を共有すべきではありません。
安定したイベント エンベロープを使用する
ビジネス イベント ID を配信試行 ID とは別にしてください。イベントは再試行されても同じままです。試行タイムスタンプと配信レコードが変更されます。
{
"event_id": "evt_01J...",
"type": "usage.finalized",
"occurred_at": "2026-10-15T14:22:31Z",
"partner_id": "partner_42",
"subject": {
"type": "usage_record",
"id": "usage_9182"
},
"schema_version": "1",
"correlation_id": "op_7fd...",
"data": {
"customer_id": "cus_204",
"currency": "USD",
"final_cost": "1.274800"
}
}
お金には 10 進数の文字列またはマイナー単位の整数を使用します。通貨、精度、丸めポリシーを文書化します。相関 ID は、イベントの ID になることなく、イベントを元のパートナー API 操作または元帳転記に接続する必要があります。
テナントセーフなファンアウトを強制する
エンドポイント固有のペイロードを構築する前に、イベントのパートナー スコープ内でサブスクリプションを解決します。ジョブまたはキュー メッセージによって提供されるエンドポイント ID は、配信を承認するのに十分であってはなりません。ワーカーは、endpoint.partner_id = event.partner_id と同等の述語を使用してエンドポイントをロードし、エンドポイントがアクティブであり、イベント タイプにサブスクライブされていることを確認する必要があります。
これは、パートナー API 自動化 におけるテナント承認のアウトバウンド対応です: オブジェクトの所有権は各非同期境界で再確立する必要があります。
送信する正確なバイトに署名します
既知の事実: 標準 Webhook 仕様では、webhook-id、webhook-timestamp、webhook-signature ヘッダーを定義し、イベント ID、試行タイムスタンプ、生のリクエスト本文に署名します。
すべてのエンドポイントに個別の署名シークレットを割り当てます。最後の JSON シリアル化の後に署名を計算し、それらの正確なバイトを送信します。署名後にオブジェクトを再シリアル化すると、空白、エスケープ、またはプロパティの順序が変更され、検証が無効になる可能性があります。
受信者は、文書化された許容範囲外のタイムスタンプを拒否し、生の本文に対して予想される HMAC を計算し、一定時間で署名を比較する必要があります。その後、イベント ID を承認する前に、一意の制約の下でイベント ID を保持する必要があります。
ローテーションの場合、2 つのアクティブなシークレットを一時的に許可し、両方の署名を含めます。パートナーが新しいシークレットを確認した後、または文書化された重複期間の後にのみローテーションを終了します。最初の作成後に既存のシークレットを再度公開しないでください。
すべての配信を監視可能かつ再生可能にします
各試行を開始時刻、ステータス コード、待ち時間、エラー カテゴリ、次回の再試行時刻、およびサイズ制限のある応答の抜粋とともに保持します。表示前にヘッダーと応答本文を編集します。オペレーターは、DNS 障害、接続拒否、TLS 障害、タイムアウト、HTTP 拒否、内部ワーカー障害
を区別する必要があります。実際的な対応ポリシーは次のとおりです:
200-299: 配達済みとマークします。410: 配信を停止し、エンドポイントを無効にします。408、425、429、500-599: 構成された制限内の有効なRetry-After値を考慮して再試行します。- その他
400-499: 永続的な受信側エラーとして分類され、公開されたポリシーに従ってのみ再試行されます。 - ネットワークとタイムアウトのエラー: 指数バックオフとジッターを使用して再試行します。
エンドポイントごとの同時実行に制限を設け、障害が発生した 1 つのレシーバーがワーカー プールを消費できないようにします。再試行を複数日に分けて行い、別のチャネルを通じてエンドポイント所有者に警告し、文書化された失敗期間の経過後にエンドポイントを無効にします。
手動再生では、元のイベントとエンドポイントのペアに対して新しい試行を作成する必要があります。新しいビジネス イベントを作成したり、event_id を変更したり、根本的なアクションが再び発生したことを暗示したりしてはなりません。
コールバック URL を SSRF 境界として扱う
パートナーが制御するコールバック URL は、ネットワーク リクエストを行う場所をインフラストラクチャに指示します。これにより、Webhook パブリッシャーが SSRF ターゲットになります。
推奨事項: HTTPS が必要で、ポートを制限し、埋め込み認証情報を拒否し、ループバック、プライベート、リンクローカル、マルチキャスト、予約済み、およびクラウドのメタデータ宛先をブロックします。検証中と接続時に再度ホスト名を解決します。最初のアドレスだけでなく、解決されたすべてのアドレスを検証します。
リダイレクトを自動的にフォローしないでください。リダイレクトがサポートされている場合は、すべての宛先ホップを検証し、小さなホップ制限を適用します。 TLS 検証用に予期されるホスト名を保持しながら、検証されたパブリック アドレスのみに接続することで、DNS 再バインドから保護します。
コントロール プレーン データベース、内部管理サービス、オーケストレーション API、インスタンス メタデータにアクセスせずに、隔離されたネットワーク セグメントでデリバリー ワーカーを実行します。リクエスト本文の制限、応答サイズの制限、接続タイムアウト、リクエストの合計期限を適用します。
重複イベントや古いイベントに対してコンシューマーを設計する
受信者は署名を認証し、タイムスタンプとスキーマを検証し、イベント ID を耐久性のある受信箱に挿入し、処理をキューに入れて、すぐに返す必要があります。一意のイベント ID 制約により、同時重複が通常のデータベース結果に変わります。
ビジネス効果には、独自の冪等性制御が必要です。 credit.adjustment_posted が会計インポートをトリガーする場合、Webhook 重複排除のみを想定するのではなく、ソース調整 ID にインポートするキーによって台帳が保護されます。タイムアウトは、受信者がその効果をコミットした後、パブリッシャーが応答を確認する前に発生する可能性があります。
キーステータスなどの変更可能な状態の場合は、古いイベントを適用する前にリソースのバージョンを比較するか、現在の状態を取得します。これは、AI API 予算管理の場合に特に重要です。遅延したしきい値通知によって、新しい制限や適用の決定が上書きされないようにする必要があります。
スキーマをバージョン化し、調整を行う
通常のパートナー API 応答とは別に、Webhook スキーマをバージョン付けします。追加フィールドの下位互換性を維持し、JSON スキーマと例を公開し、互換性のないセマンティクス用に新しいイベント タイプまたはスキーマ URI を予約します。 API が進化するたびにペイロードをサイレントに変更するのではなく、各エンドポイントを明示的なイベント スキーマの動作に固定します。
Webhook にはプルベースのリカバリも必要です:
- 認証されたパートナーをスコープとする、カーソルの後のイベントをリストします。
- 現在の顧客、キー、残高、予算の状態を取得します。
- 期間の最終的な使用状況と台帳の調整をエクスポートします。
- 配信保持と再生ウィンドウの制限を公開します。
予測: パートナーは、保持期間の期限切れ、エンドポイントの無効化、導入ミス、または長期にわたる停止により、最終的に通知を見逃すことになります。カーソルベースのイベント フィードと権限のある状態 API は、手動によるデータ修復の代わりに、そのインシデントを調整作業に変えます。
実装チェックリスト
- イベントのセマンティクス、交差ルール、データの機密性を文書化します。
- 1 つのトランザクションでイベントと権限のある変更を書き込みます。
- ファンアウトおよび配信中にパートナーの所有権を強制します。
- 安定したイベント ID、試行タイムスタンプ、生のペイロード バイトに署名します。
- 試行ごとの証拠を保存し、検索可能な配信履歴を公開します。
- 重複するシークレット、テストイベント、無効化、および再生をサポートします。
- 登録時および接続時に宛先を検証します。
- 重複、順序どおりでない配信、永続受信後のタイムアウト、ローテーションの重複、DNS 再バインド、プライベート IP リダイレクト、レート制限、および長時間のダウンタイムをテストします。
- 送信トレイの経過時間、キューの深さ、成功率、エンドポイントの無効化、再試行の飽和を監視します。
- 配信レコードの有効期限が切れた後も調整 API を利用できるようにします。
信頼できるレコードに基づいて通知を作成する
信頼できる設計は、より攻撃的な再試行ループではありません。これは、コミットされた状態、トランザクション イベントの作成、テナント スコープのファンアウト、署名された配信、冪等の受信、プル ベースの調整などの永続的な境界のチェーンです。イベント カタログを拡張する前に、これらの境界を実装してください。これにより、パートナーは、信頼性の低い HTTP 交換を記録システムとして扱うことなく、プロビジョニング、アクセス制御、残高、予算、使用状況を自動化できます。