Webhook

セッションのイベントをバックエンドで受け取れます。すべての配信は HMAC-SHA256 で署名され、指数バックオフで再試行され、受信側を冪等にできるよう固有の ID が付きます。

webhook を使う場面

ポーリングせずにルームの出来事に反応するためのものです。セッションの開始と終了、質問の締め切り、結果の確定のたびにプラットフォームから呼び出します。配信は「少なくとも1回」で、重複することはあっても黙って失敗することはありません。だからこそ受信側は冪等である必要があります。

イベントカタログ

API の /public/v1/limits から配信されます。このページは写しを持ちません。

  • presentation.import.completed
  • presentation.import.failed
  • question.activated
  • question.closed
  • question.results.finalized
  • report.ready
  • session.ended
  • session.paused
  • session.resumed
  • session.started

すべての配信に付くヘッダー

  • X-Votinova-Eventイベントの種類。カタログの表記そのままです。
  • X-Votinova-Delivery-Id配信ごとに固有。重複排除のキーになります。
  • X-Votinova-Timestampエポック秒。署名の対象に含まれるため、再送された配信の日時を書き換えることはできません。
  • X-Votinova-Signature16進数の HMAC-SHA256。接頭辞は付きません。

本文はイベントと関係する ID をすべて含むコンパクトな JSON です。以下は署名済みの配信例そのもの(バイト単位で同一)です。

{"event":"session.ended","session_id":"68b2e3d4c5f607182930a5bc"}

署名の検証

すべての配信は、タイムスタンプ、ピリオド、本文をつないだ文字列に対して、エンドポイントのシークレットを鍵とした HMAC-SHA256 で署名されています。その結果を X-Votinova-Signature ヘッダーと比較してください。

  • 署名は生の本文に対して行ってください。解析して書き出し直すと空白が変わり、署名が一致しなくなります。
  • ==ではなく、一定時間で比較してください。
実際の配信で検証済み
const crypto = require("node:crypto");

function verify(headers, rawBody, secret) {
  const timestamp = headers["x-votinova-timestamp"];
  const signature = headers["x-votinova-signature"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
secret     whsec_3f8a1c92d4e5b6a7
timestamp  1785321600
body       {"event":"session.ended","session_id":"68b2e3d4c5f607182930a5bc"}

HMAC-SHA256(secret, timestamp + "." + body)
= 10a86171bd92f5fcf8c28b873978e0214302cc2291c54cc016d18347f3ee7c38

再試行

webhook の配信は最大8回まで再試行し、待ち時間は21, 45, 95, 171, 369, 732, 1480秒と長くなっていきます。使い切ると配信はデッドレターに送られ、手動で再配信できます。

冪等性

重複排除のキーには X-Votinova-Delivery-Id をお使いください。同じ配信が複数回届くことがあり、処理は一度だけ効かせる必要があります。

X-Votinova-Delivery-Id

おすすめの実装

  • 配信を安全に受け取れた時点ですぐ200を返し、処理は自前のキューで行ってください。受信が遅いと再試行が積み上がります。
  • 解析する前に必ず署名を検証してください。検証しなければ、URL を知っている人は誰でもイベントを偽造できます。
  • X-Votinova-Delivery-Id で重複排除してください。「少なくとも1回」とは、同じ配信が二度呼ばれうるという意味です。
  • 10回続けて失敗するとエンドポイントは自動で停止します。受信側が復旧したら、パネルまたは API から再度有効にしてください。