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 から再度有効にしてください。