Webhook の連携
アプリで Webhook イベントを受け取るには、以下の手順に従って Webhook エンドポイントを作成し、登録してください。
- Webhook エンドポイントハンドラーを作成し、イベントデータの POST リクエストを受信する
- Tokenz Dashboard を使用してエンドポイントを登録する
- Webhook エンドポイントを保護する
- テストモードでの支払い により Webhook エンドポイントのテストをする
1) ハンドラーを作成する
Webhook リクエストを POST メソッドで受け取ることができる HTTPS エンドポイント関数を設定します。 ペイロードの確認及び配信のテストについては、Webhook.site を参照してください。
エンドポイント関数が以下を満たしていることを確認してください。
- イベントオブジェクト で構成される JSON ペイロードで POST リクエストを処理する
- タイムアウトを引き起こす可能性のある複雑なロジックを実行する前に、迅速に成功ステータスコード (2xx) を返す
2) エンドポイントを登録する
Tokenz Dashboard で Webhook エンドポイントを登録します。
ホームに移動する新しい webhook を作成をクリックする- URL と任意の説明を入力し、受信したいイベントを選択する (お客様の注文が成功したら必ず把握できるよう、最低でも order.succeeded を選択することをお勧めします。)
Webhook を作成をクリックする- 返された署名用シークレットをコピーして保存する (受信した Webhook を検証するために必要です。 セキュリティ上の理由から、シークレットは一度しか表示されません。)

3) ウェブフックエンドポイントを保護する
Tokenz は送信するすべての Webhook に署名を含めてます。 この署名を検証し、Webhook が Tokenz から送信されたものであり、改ざんされていないことを確認する必要があります。 別の方法として、通知を単にトリガーとして使用し、API を介して注文ステータスの変更を確認することもできますが、 不要な API 呼び出しを避けるためにも、Webhook 署名の検証をお勧めします。
署名されたイベントには、検証が必要なタイムスタンプ (t:<unix-timestamp-in-milliseconds>) と 1 つまたは複数のバージョン付き署名 (v1:<HMAC signature>) を含む Tokenz-Signature ヘッダーが含まれています。 現在、有効な署名スキームは v1 です。
例:
t:1725864981111,v1:zjdrra3...Mh+e6s=
署名の検証
1) タイムスタンプと署名を抽出する
ヘッダーをカンマ (,) で区切り、エレメントのリストを作成します。 次に、各要素をコロン (:) で区切り、プレフィックスと値のペアを取得します。 プレフィックス t はタイムスタンプを表し、 v1 には署名が含まれます。 他のエレメントは無視してください。
2) 署名用のペイロードを準備する
以下を組み合わせてペイロードの文字列を作成します。
- タイムスタンプ (文字列として)
- 逐語的なリクエストボディ (つまり、JSON ペイロード)
3) 署名を計算する
SHA256 ハッシュ関数を使って HMAC を計算します。 エンドポイントの署名用シークレットをキーとして、ペイロードの文字列をメッセージとして使用します。
4) 署名を比較する
ヘッダーの署名と想定される署名を比較します。 現在のタイムスタンプと受信したタイムスタンプの違いを評価し、リプレイ攻撃を防ぎます。 署名が有効でもタイムスタンプが古い場合はそのペイロードを拒否することができます。 推奨される許容時間は 300 秒 (5 分) です。
リプレイ攻撃は、攻撃者が正当なペイロードとその署名を傍受し再送信することにより行われます。 Tokenzでは Tokenz-Signature ヘッダーにタイムスタンプを含めることでリプレイ攻撃を防いでいます。
const crypto = require('crypto');
const express = require('express');
const app = express();
const port = 3000;
// Use express.raw() so the signature is computed over the exact bytes
// Tokenz sent; re-serializing a parsed body can fail verification.
// If your app registers express.json() or another body parser globally,
// mount this webhook route BEFORE it so the raw body is preserved.
app.post("/webhook", express.raw({ type: 'application/json' }), (req, res) => {
// The signing secret shown once on webhook creation is Base64-encoded.
const secret = 'webhook-endpoint-secret';
const sig = req.headers['tokenz-signature'];
let timestamp, signature;
for (let sigPart of sig.split(',')) {
let [key, value] = sigPart.split(':');
if (key === 't') {
timestamp = value;
} else if (key === 'v1') {
signature = value;
}
}
const checkSignature = crypto.createHmac('sha256', Buffer.from(secret, 'base64'))
.update(`${timestamp}${req.body.toString('utf8')}`)
.digest('base64');
// The `t` value in Tokenz-Signature is a Unix timestamp in milliseconds.
const tolerance = 5 * 60 * 1000; // 5 minutes
if (signature === checkSignature && Math.abs(Date.now() - Number(timestamp)) <= tolerance) {
return res.status(200).end();
} else {
return res.status(400).end();
}
});
4) Webhook エンドポイントをテストする
テストガイド に従ってテストモードでの支払いを作成して完了し、Webhook の処理が正しく機能していることを確認してください。