決済 Webhook
署名付きの決済・チェックアウト・返金イベントをバックエンドで受信します。
RANTAN はホステッド決済、チェックアウト、返金の状態が変わると、署名付き HTTPS イベントを販売者バックエンドへ送信します。Webhook は非同期通知なので、検証して冪等に処理してください。
#Endpoint を登録
販売者 JWT で HTTPS endpoint を登録します。プライベート・loopback・ローカルネットワーク・認証情報付き URL・リダイレクト・非標準ポートは許可されません。
curl -X POST https://app.rantan.xyz/api/v2/webhooks/endpoints/ \
-H "Authorization: Bearer $RANTAN_SELLER_JWT" \
-H "Content-Type: application/json" \
-d '{
"url": "https://merchant.example/webhooks/rantan",
"description": "Production",
"subscribed_events": [
"payment.succeeded",
"checkout.completed",
"checkout.failed",
"refund.completed"
]
}'{
"id": "7d97ad12-7091-43f6-b9bc-707cab54ae29",
"url": "https://merchant.example/webhooks/rantan",
"description": "Production",
"subscribed_events": [
"payment.succeeded",
"checkout.completed",
"checkout.failed",
"refund.completed"
],
"is_active": true,
"created_at": "2026-07-30T10:00:00Z",
"updated_at": "2026-07-30T10:00:00Z",
"signing_secret": "whsec_store-this-once"
}#イベントタイプ
| イベント | 送信タイミング |
|---|---|
payment.succeeded | x402 決済が永続的に記録された時 |
checkout.completed | RANTAN のフルフィルメントが完了した時 |
checkout.failed | チェックアウトが最終失敗状態になった時 |
checkout.canceled | セッションまたは購入がキャンセルされた時 |
checkout.expired | セッションまたは購入が期限切れになった時 |
refund.completed | 返金完了が記録された時 |
#イベント Payload
各イベントには固定 id があります。アクセス、在庫、クレジット、注文状態を変更する前に一意キーとして保存してください。
{
"id": "d22f4de0-4e92-4dc9-af9f-aaadbbdad801",
"type": "payment.succeeded",
"api_version": "2026-07-30",
"created_at": "2026-07-30T10:04:32Z",
"data": {
"object": {
"checkout_session_id": "c8a9d48e-68b2-48dd-a754-75635cae71d6",
"client_reference_id": "order_20260730_001",
"product_id": 123,
"purchase_id": "bfaf2aa4-dd86-4356-92db-ae0431806867",
"purchase_status": "payment_confirmed",
"completed": false,
"buyer_wallet_address": "0x1234...abcd",
"quantity": 1,
"currency": "USDC",
"total_amount": "12.50000000",
"amount_atomic": "12500000"
},
"payment": {
"provider": "x402",
"transaction_hash": "0xabcd...",
"amount_atomic": "12500000",
"recipient": "0x9876...fedc"
}
}
}#署名を検証
Rantan-Signature は t={Unix timestamp},v1={HMAC-SHA256} 形式です。正確な raw body を `${t}.${rawBody}` として digest を計算し、5分を超えた timestamp を拒否して定数時間で比較します。
import { createHmac, timingSafeEqual } from "node:crypto";
export async function handleRantanWebhook(req, res) {
// Read the raw bytes before JSON parsing.
const rawBody = await readRawBody(req);
const signatureHeader = req.headers["rantan-signature"];
if (typeof signatureHeader !== "string") {
return res.status(400).end();
}
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => part.split("=")),
);
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) {
return res.status(400).end();
}
const expected = createHmac(
"sha256",
process.env.RANTAN_WEBHOOK_SECRET,
)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex");
const received = Buffer.from(parts.v1 ?? "", "hex");
const calculated = Buffer.from(expected, "hex");
if (
received.length !== calculated.length ||
!timingSafeEqual(received, calculated)
) {
return res.status(401).end();
}
const event = JSON.parse(rawBody.toString("utf8"));
await processEventOnce(event.id, event.type, event.data);
return res.status(204).end();
}#再試行と配信履歴
10秒以内に 2xx を返してください。リダイレクトは追跡しません。失敗時は1分、5分、30分、2時間、6時間、24時間、48時間後に再試行し、8回失敗すると failed になります。
#手動再試行
直近100件までの配信履歴を確認し、配信済みまたは失敗したイベントを再送できます。イベント id は変わらないため、受信処理は必ず冪等にしてください。
curl -X POST \
https://app.rantan.xyz/api/v2/webhooks/deliveries/$DELIVERY_ID/retry/ \
-H "Authorization: Bearer $RANTAN_SELLER_JWT"#Webhook API
/api/v2/webhooks/endpoints/Endpoint を作成し、最初の signing_secret を受け取ります。
/api/v2/webhooks/endpoints/販売者の endpoint 一覧を取得します。
/api/v2/webhooks/endpoints/{endpointId}/URL、購読イベント、有効状態を変更します。
/api/v2/webhooks/endpoints/{endpointId}/特定 endpoint の設定を取得します。
/api/v2/webhooks/endpoints/{endpointId}/Endpoint を無効化し、以降の配信を停止します。
/api/v2/webhooks/endpoints/{endpointId}/rotate-secret/以前の secret を無効化し、新しい signing_secret を取得します。
/api/v2/webhooks/deliveries/配信状態とレスポンス履歴を取得します。
/api/v2/webhooks/deliveries/{deliveryId}/retry/同じ event id で配信を再試行します。