コンテンツにスキップ

Webhook

Webhookを使うと、チケットの参加・呼び出し・完了といったキューイベントが発生した際に、あなたのシステムがリアルタイムのHTTP POST通知を受け取れます。

  1. admin.jonot.io/settings/integrationsを開きます。
  2. エンドポイントを追加をクリックします。
  3. サーバーがリッスンする公開HTTPS URLを入力します。
  4. 購読するイベントを選択します。
  5. 保存をクリックします。

署名シークレットは一度しか表示されないため保存しておいてください。エンドポイントに到達可能かを確認するには、エンドポイント編集ページのテストリクエストを送信ボタンを使用します。

イベント名発火するタイミング
ticket.joined顧客がキューに参加したとき
ticket.calledスタッフがチケットをサービスデスクに呼び出したとき
ticket.completedチケットが完了とマークされたとき
ticket.cancelled顧客またはスタッフがチケットをキャンセルしたとき
ticket.skippedチケットがスキップされたとき(呼び戻し可能な保留)
ticket.no_show呼び出し済みまたはスキップ済みのチケットが不在と確定したとき
queue.status_changedキューのステータスが変わったとき(ACTIVEPAUSEDCLOSED

すべての配信は、次のヘッダーを持つHTTP POSTです。

Content-Type: application/json
X-Jonot-Event: ticket.called
X-Jonot-Delivery-Id: <uuid>
X-Jonot-Timestamp: 2024-06-01T12:00:00.000Z
X-Jonot-Signature: v1,<base64-hmac-sha256>
X-Jonot-Payload-Version: v1

デフォルトの本文はJSONエンベロープです。payloadフィールドの形はイベントごとに異なります。

ticket.joined

{
"deliveryId": "<uuid>",
"event": "ticket.joined",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"ticket": { "id": "tkt_…", "queueId": "q_…" },
"queue": { "id": "q_…", "name": "Main Queue" },
"location": { "id": "loc_…", "name": "Downtown" }
}
}

ticket.called / ticket.completed / ticket.skipped / ticket.no_show

{
"deliveryId": "<uuid>",
"event": "ticket.called",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"ticket": {
"id": "tkt_…",
"number": 42,
"status": "CALLED",
"queueId": "q_…",
"createdAt": "2024-06-01T11:58:00.000Z",
"updatedAt": "2024-06-01T12:00:00.000Z"
}
}
}

ticket.cancelled

{
"deliveryId": "<uuid>",
"event": "ticket.cancelled",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"ticketId": "tkt_…",
"queueId": "q_…"
}
}

queue.status_changed

{
"deliveryId": "<uuid>",
"event": "queue.status_changed",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"queueId": "q_…",
"status": "PAUSED"
}
}

Jonotは、次の文字列に対してHMAC-SHA256で各配信に署名します。

<deliveryId>.<timestamp>.<body>

Node.js(18以上)で検証する方法:

import { createHmac, timingSafeEqual } from "node:crypto";
/**
* Returns true when the signature header is valid and the timestamp is
* within 5 minutes of now. Throws for malformed input.
*/
function verifySignature(secret, deliveryId, timestamp, body, header) {
// Replay-attack guard: reject deliveries older than 5 minutes.
const ageMs = Date.now() - new Date(timestamp).getTime();
if (Math.abs(ageMs) > 5 * 60 * 1000) return false;
const expected =
"v1," +
createHmac("sha256", secret)
.update(`${deliveryId}.${timestamp}.${body}`)
.digest("base64");
// timingSafeEqual prevents timing-oracle attacks.
// Buffers must be the same length — if lengths differ the signature is
// invalid, but we still compare a dummy value to keep constant time.
const expectedBuf = Buffer.from(expected);
const headerBuf = Buffer.from(header);
if (expectedBuf.length !== headerBuf.length) return false;
return timingSafeEqual(expectedBuf, headerBuf);
}

デフォルトのJSON本文を、独自のテンプレートに置き換えられます。テンプレートは簡易版Mustache構文を使います。

  • {{ path.to.value }} — 値が置換され、コンテンツタイプに応じてエスケープされます。application/jsonではJSON文字列としてエスケープ、application/x-www-form-urlencodedではパーセントエンコード、text/plainではそのまま出力されます。
  • {{{ path.to.value }}} — エスケープなしでそのまま置換されます。

application/json用のテンプレート例(ticket.joinedを購読している場合):

{
"type": "{{ event }}",
"ticketId": "{{ payload.ticket.id }}",
"queueName": "{{ payload.queue.name }}"
}

ペイロードテンプレートのフィールドは、次の機能を備えた本格的なコードエディタです。

  • JSONテンプレート向けのシンタックスハイライトと括弧の対応表示
  • 変数パレット — 選択したイベントで利用可能な変数パスごとに、常時表示される挿入ボタンの行。ボタンをクリックすると、カーソル位置に{{ path }}トークンが挿入されます。
  • リアルタイム検証 — 入力中に、エディタはJSON構造(コンテンツタイプがapplication/jsonで、かつ生の{{{ }}}タグが含まれない場合)と変数パスの両方をチェックします。診断結果はエディタ内のインラインマーカーと、その下のサマリーバナーとして表示されます。
    • エラー — 選択したすべてのイベントで存在しないパス。
    • 警告 — 選択したイベントの一部にのみ存在するパス(他のイベントの配信では空になります)。

検証は保存時にサーバー側でも実行され、エディタのフィードバックはサーバー側のルールと完全に一致します。

失敗した配信(2xx以外のステータスまたは接続エラー)は、Cloudflare Queuesによって指数バックオフで最大3回まで再試行されます。3回失敗すると、その配信はデッドレターキューに入り、エンドポイントの連続失敗カウンターが増加します。

エンドポイントが連続20回の失敗を蓄積すると、自動的に無効化されます。エンドポイント編集ページから再度有効化すると、カウンターはゼロにリセットされます。

各エンドポイントの配信タブには、過去30日間の配信試行(イベントタイプ、HTTPステータス、試行回数、タイムスタンプ)が表示されます。さらに読み込むボタンで過去の記録をページ送りできます。

項目
組織あたりのエンドポイント数5
エンドポイントあたりのカスタムヘッダー数10
ヘッダー値の長さ1,024バイト
ペイロードテンプレートのサイズ16KB
配信タイムアウト10秒
配信履歴の保持期間30日
組織あたりの最大配信レート120回 / 60秒

署名シークレットのローテーション

Section titled “署名シークレットのローテーション”
  1. エンドポイント編集ページを開きます。
  2. 署名シークレットをローテーションをクリックします。
  3. ダイアログでローテーションを確認します。
  4. 新しいシークレットをすぐにコピーしてください — これは一度しか表示されず、復元できません。保存せずにダイアログを閉じた場合、新しい平文を取得するにはもう一度ローテーションする必要があります。
  5. 新しいシークレットで署名を検証するようにサーバーを更新します。

猶予期間はありません。 古いシークレットは、ローテーションを確認した瞬間から検証に使えなくなります。ローテーション後は、すぐに受信側に新しいシークレットをデプロイできるよう計画してください。

UIには、ローテーションボタンの隣にActive secret: ····XXXX(末尾4文字)が表示され、現在有効になっているシークレットを確認できます。ローテーション後に受信側とサーバーが同期しているかを確認する際に便利です。

ローテーションすべきタイミング:

  • 漏えいが疑われる場合: シークレットがログファイルに出現した、退職する従業員と共有していた、画面録画に映り込んだなど。
  • 定期的な運用衛生: 定期的にローテーションすることで、検出されていない漏えいの被害範囲を限定できます。
  • シークレットを読み取れるサービスに変更があった場合(鍵管理のローテーション)。

受信側の更新方法:

  1. Admin UIでローテーションし、新しいシークレットをコピーします。
  2. 受信側のシークレットストア(環境変数、シークレットマネージャーなど)を更新します。
  3. 更新した受信側をデプロイします。
  4. 配信タブで次の配信が成功することを確認します。

保存前に新しいシークレットを紛失した場合:

再度ローテーションしてください。ローテーションのたびに新しいランダムなシークレットが生成されます。以前の平文は復元できず、サーバー側にはAEAD暗号化された形式のみが保存されています。