Webhook
スマートフォンがアクティビティを追加したとき、またはユーザーがオフにしたり削除したりしたときに、バックエンドに送られる署名付きの呼び出し。
Webhookエンドポイントは、サービスがPOSTを送るURLです。エンドポイントはダッシュボードで追加します。それぞれに専用の署名シークレット(whs_と48桁の16進数)があり、識別子を付けられます。同じチーム内の2つのエンドポイントに同じ識別子は使えません。URLはhttpまたはhttpsにする必要があります。
識別子によるルーティング
webhookIdentifierを付けて開始したアクティビティは、すべてのイベントをその識別子を持つ1つのエンドポイントに送ります。付けずに開始したアクティビティは、識別子のないすべてのエンドポイントにイベントを送ります。一致するエンドポイントがないイベントは破棄されます。
テストもこの方法で行います。別のテスト環境はありません。本番のエンドポイントは識別子なしのままにして、テスト用のアクティビティはテスト用エンドポイントの識別子を付けて開始します:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
curl -X POST https://api.webliveactivity.com/api/start \ -H 'Content-Type: application/json' \ -H 'x-api-key: wla_your_key' \ -d '{ "widgetContainerId": "acme-orders", "webhookIdentifier": "staging", "brand": { "title": "Order #1234" }, "state": { "layout": "progress", "primaryText": "Order received", "progress": 0.1 } }'
イベント
| イベント | 送信されるタイミング |
|---|---|
active | スマートフォンがアクティビティを開いて画面に表示された(スマートフォンごとに1回)、またはユーザーがアプリで再びオンにした。 |
removed | ユーザーがアプリでアクティビティをオフにした、削除した、またはアプリのデータを消去した。 |
POST /api/endと再起動ではWebhookは送られません。
配信
各イベントは、JSON本文を持つ1回のPOSTです:
1 2 3 4 5 6 7 8
{ "activityId": "act-8f2cqa4mx7rd", "group": "order-tracker", "event": "active", "active": true, "webhookIdentifier": "staging", "at": "2026-07-30T14:25:19.991Z" }
| フィールド | 意味 |
|---|---|
activityId | アクティビティ。reuse: trueの場合は、startが返したIDではなく、そのスマートフォン専用のコピーです。 |
group | アクティビティのグループ。設定されていない場合は""。 |
event | activeまたはremoved。 |
active | その時点でアクティビティが画面に表示されているかどうかのフラグ。このフラグではなく、eventに基づいて処理します。 |
webhookIdentifier | startで指定した識別子。設定されていない場合はnull。 |
at | イベントが発生した時刻(ISO形式)。 |
ヘッダーは3つです:
| ヘッダー | 意味 |
|---|---|
X-WebActivities-Event | イベント名:activeまたはremoved。 |
X-WebActivities-Timestamp | この試行が送信された時刻のUnixエポック秒。再試行のたびに新しくなります。古いタイムスタンプは拒否してください。 |
X-WebActivities-Signature | 下記のHMAC署名。 |
署名の検証
署名は、タイムスタンプと未加工のリクエスト本文を、エンドポイントのシークレットをキーとしてHMAC-SHA256で計算したものです。タイムスタンプは署名対象のペイロードに含まれるので、傍受した配信を新しいタイムスタンプで再送することはできません。
1 2 3 4 5 6 7 8 9 10 11 12
const { createHmac, timingSafeEqual } = require('node:crypto'); // rawBody is the request body EXACTLY as received, before any JSON parsing. const verify = (secret, headers, rawBody) => { const timestamp = headers['x-webactivities-timestamp']; const expected = 'sha256=' + createHmac('sha256', secret) .update(timestamp + '.' + rawBody) .digest('hex'); const presented = Buffer.from(headers['x-webactivities-signature']); return expected.length === presented.length && timingSafeEqual(Buffer.from(expected), presented); };
誤った署名から正しい署名の情報が漏れないよう、定数時間で比較します(timingSafeEqual)。また、リプレイを防ぐため、タイムスタンプをローカルの時計と照合します。
再試行
- エンドポイントは8秒以内に2xxで応答する必要があります。リダイレクトは失敗とみなされます。
- 失敗した配信は、1分、2分、4分、8分の間隔をあけて、合計5回試行されます。
- プライベートアドレスや内部アドレスを指すURLは拒否されます。
- ダッシュボードの配信ログには90日間のすべての配信が表示され、そこから再送信もできます。
すばやく応答する
まずイベントを保存して200を返し、そのあとで処理します。リクエスト内で時間のかかる処理をすると、8秒のタイムアウトに達し、再試行が重複して届くおそれがあります。再試行は順不同で届くことがあるので、イベントの実際の時刻にはatフィールドを使います。









