配信の動作

標準ペイロード、5秒の制限時間、再試行が行われない理由、各配信ステータスの意味を説明します。

Webhook の配信は意図的にシンプルに設計されていますが、その設計は受信側の実装に影響します。HTTP リクエストは 1 回のみ、制限時間は 5 秒、再試行はありません。ハンドラーで同期的に実行する処理の範囲を決める前に、このページをお読みください。

標準ペイロード

listing.submission 以外のすべてのイベントは、トップレベルの構造が共通で、イベント固有のフィールドは data 内に格納されます。

JSON
{  "event": "connection.location_connected",  "timestamp": "2026-08-25T14:32:10Z",  "account_id": 11073,  "location_id": "279381",  "data": {    "platform": "google",    "connected_account_id": "ba03bc4b-9f8c-4d3b-8e93-9628034c63cc"  }}
フィールド型注記
eventstringドット区切りのイベント名です。この値に基づいて処理を分岐してください。
timestampstringISO-8601 形式の UTC 時刻で、元の変更が発生した時点ではなく、配信データが作成された時点で付与されます。
account_idintegerイベントが属するアカウントです。
location_idstring数値を表す文字列です。279381 ではなく、"279381" です。interaction.* では型変換されません。以下の例外を参照してください。
dataobjectイベント固有のフィールドです。イベントリファレンス にイベントごとの説明があります。
agency_account_idintegerアカウントが親代理店アカウントによって管理されている場合にのみ存在します。

data 内の省略可能なフィールドは、空の場合、null として送信されるのではなく省略されます。想定外の入力にも対応できるように、キーが存在しない場合と明示的な null を同じように扱い、省略可能と記載されているキーが必ず存在するとは考えないでください。

2 つの例外

5 秒の制限時間

各配信は 1 回の POST で行われ、接続タイムアウトは 5 秒、読み取りタイムアウトは 5 秒です。エンドポイントが制限時間内に応答しない場合、その試行は中止され、timeout として記録されます。

この制限時間には、ハンドラーの処理全体が含まれます。安全に処理するには、次の設計にする必要があります。

  1. 署名を検証します。
  2. ペイロードをキュー、テーブル、ログなどの永続的な保存先に書き込みます。
  3. 200 を返します。

それ以外のすべての処理(データの補完、複数の処理先への振り分け、REST API の呼び出し、サードパーティーにアクセスするあらゆる処理)は、キューから取り出した後に実行します。

再試行はありません

配信は必ず 1 回だけ試行されます。バックオフも、再試行キューも、デッドレターキューもありません。エンドポイントが 2 分間停止した場合、その 2 分間に発生したイベントは失われます。後から届くことはありません。

この仕様による影響として、次の 2 点を明確にしておきます。

  • リダイレクトは追跡されません。リダイレクトは完全に無効化されているため、エンドポイントが 301 または 302 を返すと、別の URL への追跡は行われず、配信失敗として記録されます。Webhook の URL には最終的な URL を設定してください。多くの Web サーバーがデフォルトで追加する、ベアドメインから www へのリダイレクトや、末尾のスラッシュに関するリダイレクトに注意してください。
  • 2xx の場合のみ配信済みと見なされます。3xx、4xx、5xx はすべて失敗として記録されます。

重複と順序

  • 同じイベントを2回受信することも、異なる2つの変更で同じidを受信することもあります。 data内の識別子(インタラクションid、投稿項目id、接続済みアカウントid)は、変更ではなくリソースを識別します。レビューが変更されるたびに、interaction.reviewは同じインタラクションidで再度発火し、どのペイロードにもバージョンやupdated-atフィールドが含まれないため、id + eventを基準にイベントを恒久的に除外するハンドラーは、実際の編集を通知なく破棄してしまいます。代わりに、完全に一致する重複を検出してください。ボディの生バイト列のハッシュを計算し、短い有限の時間枠(無期限ではなく数分間)内に同じハッシュが再度現れた場合に無視します。同じidでもボディが異なる場合は、重複ではなく新しい変更です。その後、REST APIからリソースを再取得し、その取得結果を正しい状態として扱ってください。書き込みをスキップせず、リソースidをキーにして書き込んでください。
  • 順序は保証されず、timestampを使っても解決しません。イベントは複数の独立した経路から発生し、到着した順に配信されるため、先に届くと想定していたlocal_post.createdよりもlocal_post.publishedが先に届くことがあります。同じリソースに対する2つの変更を、timestampで並べ替えないでください。これは変更が発生した時点ではなく配信が作成された時点で付与され、ペイロードのどこにもバージョンやシーケンス番号がないためです。順序が重要な場合は、リソースを再取得し、APIが返す内容を使用してください。
  • profile.updatedにはデバウンス処理が適用され、拠点ごとに60秒間に1回、時間枠の開始時に配信されます。これは遅延ではなく、実際にイベントを間引く処理です。最初の編集によって時間枠が開始され、その編集が配信されますが、時間枠内の編集はすべて破棄されて届かず、changed_fieldsには時間枠を開始した編集だけが記述されます。そのため、イベントが届くのは時間枠の開始時であり、到着をきっかけに再取得しても、その最初の編集しか確認できません。時間枠が終了した後に2回目の再取得を行うよう、イベントから少なくとも60秒後にスケジュールし、タイマーによる拠点の照合も行ってください。通知自体が失われることもあるためです。配信の試行は1回のみで、再試行はありません。それ以外にはデバウンス処理は適用されません。

イベントを受信できなかった場合の復旧

再試行も再配信もないため、どのイベント種別にも、代わりに正しい状態を取得できる読み取り手段が必要です。異常が疑われるときだけでなく、定期的に照合してください。

イベント種別再取得に使用するもの推奨間隔読み取りでは復元できない情報
listing.submissionGET /locations/{locationId}/listings/premiumを、listingType=premiumで1回、additionalで1回使用します。イベントが通知するサイトごとのsyncStatusとlistingUrlを取得します。拠点ごとに毎日、および拠点の一括編集後です。旧形式のペイロードにはtimestampもaccount_idもないため、受信できなかったイベントの発生時点や所属アカウントを特定できません。incompleteの送信に含まれるerror_messageは、どの読み取りでも取得できません。
profile.*GET /locations、GET /locations/search、またはGET /locations-by-idsを使用します。各拠点のupdatedAtから、どの拠点に変更があったかを確認できます。1時間ごとに加え、前述の時間枠終了後の再取得も行います。source(user / api / bulk_edit)とchanged_fieldsです。読み取りで取得できるのは現在の状態であり、誰が何を変更したかは取得できません。60秒間のデバウンス処理で間引かれた編集は一切記録に残らず、削除された拠点の削除前の状態も失われます。
connection.*接続、切断、認証の正常性の確認には、GET /connected-accountsとGET /connected-accounts/{connectedAccountId}/detailsを使用します。Googleの確認に関する2種類のイベントについては、いずれかの拠点の読み取り結果からgoogleVerificationStatus { status, message }を取得してください。1時間ごとです。確認処理の進行中は数分ごとです。failure_type(rejectedかtransientか)とreasonの文字列です。接続と切断の繰り返しは確認できません。一度切断されてから復旧した接続は、接続済みとして読み取られます。
interaction.*拠点ごとにはGET /locations/{locationId}/reviews、拠点を横断する場合はGET /rollup_interactions、idで取得する場合はGET /reviewDetailsを使用します。新しいレビューは15分ごと、より広い範囲の確認は毎日です。何かが変わったという事実です。ドキュメントに記載されているインタラクションのフィールドにはupdated-atがないため、編集されたレビューも、すでに保存したレビューとまったく同じに見えます。保存しているフィールドを比較してください。
local_post.*サイトごとの現在の公開ステータスの取得には、GET /locations/{locationId}/posts、GET /locations/{locationId}/bulk-posts、GET /posts/{postId}を使用します。投稿処理の進行中は1時間ごとです。配信時のrejection_reasonと、投稿項目がたどった状態の順序です。読み取りで取得できるのは現在のステータスであり、状態の遷移ではありません。
review_analytics.*対象期間のstartDateとendDateを指定して、GET /locations/{locationId}/review-analytics-overviewを使用します。毎日、集計時間枠の終了後です。週次のイベント種別については毎週です。その時点の数値です。概要は読み取り時に再計算されるため、遅れて届いたレビューや削除されたレビューによって、スナップショットに含まれていた内容と異なる結果になります。

配信結果

外部への送信に至らなかった場合も含め、すべての配信試行にステータスが記録されます。以下は、実際に使用される文字列です。

4つの配信結果と、それぞれを記録するステータス文字列です。「ブロック」と「レート制限」は、HTTPリクエストがまったく行われなかったことを意味するため、実際の配信失敗と区別する必要があります。
ステータス結果意味対応方法
success配信成功エンドポイントが2xxを返しました。対応は不要です。
文字列形式のHTTPステータスコード、e.g。"404"、"500"失敗エンドポイントが、記録された2xx以外のコードを返しました。リダイレクトは追跡しないため、3xxもここに含まれます。パス、リダイレクトチェーン、ハンドラーのエラー率を確認してください。
999失敗レスポンスは返されましたが、有効なステータスコードがありませんでした。通常、エンドポイントの前段にあるプロキシまたはロードバランサーが原因です。
timeout失敗制限時間の5秒以内にレスポンスがありませんでした。処理をリクエスト処理から切り離し、受信確認をより早く返してください。
network_error失敗ホストの名前を解決できなかったか、ホストに到達できませんでした。DNSの設定と、ホストの名前をパブリックDNSで解決できることを確認してください。
error失敗配信試行中に、TLSハンドシェイクの失敗など、上記のいずれにも該当しないエラーが発生しました。証明書チェーンとサーバーログを確認してください。
blocked_urlブロック接続時のURLの安全性チェックに失敗しました。HTTPSではない、ホストがない、または名前解決の結果がプライベートアドレス、ループバックアドレス、リンクローカルアドレス、メタデータアドレスのいずれかだったことが原因です。URLが公開HTTPSホストを指すようにしてください。このチェックは配信のたびに行われるため、DNSの変更によって、それまで機能していたURLがブロックされる場合があります。
endpoint_unverifiedブロックアカウントには署名用シークレットがありますが、エンドポイントが検証に合格していないか、検証がリセットされています。ダッシュボードからエンドポイントを検証を再実行してください。
rate_limitedレート制限1分あたり、または1日あたりの上限を超えました。イベントは破棄されました。配信量を減らすか、上限がより高いプランに変更してください。プランと上限を参照してください。

受信側のチェックリスト

  • すべてのリクエストで署名を検証し、一致しない場合は401を返して、何も処理しないでください。
  • 生のリクエストボディのバイト列をハッシュ化し、再シリアライズしたオブジェクトは決して使用しないでください。
  • 処理はキューに入れ、5秒の制限時間に十分な余裕を持って2xxを返してください。
  • ハンドラーによる各書き込みではdata内のIDをキーにして、繰り返し実行されても同じ結果になるようにしてください。ただし、同じIDを再度受信したからといって、処理が不要だと判断しないでください。
  • 到着順から順序を推測せず、timestampによる並べ替えも行わないでください。
  • 受信時にlocation_idを文字列に正規化してください。
  • 認識できないeventの値は無視し、その場合も200を返してください。
  • WebhookのURLではリダイレクトせず、そのURLで直接リクエストを受け付けてください。
  • REST APIと定期的に照合してください。再試行による補完はありません。