Webhooks
Receive signed payment, checkout, and refund events on your backend.
RANTAN sends signed HTTPS events to your backend when a hosted payment, checkout, or refund changes state. Webhooks are asynchronous signals; verify and fulfill each event idempotently.
#Register an endpoint
Register an HTTPS endpoint with the seller JWT. Private, loopback, local-network, credential-bearing, redirecting, and non-standard-port destinations are rejected.
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"
}#Event types
| Event | When it is sent |
|---|---|
payment.succeeded | The x402 settlement is recorded durably. |
checkout.completed | RANTAN fulfillment completed successfully. |
checkout.failed | Checkout reached a terminal failed state. |
checkout.canceled | The session or linked purchase was canceled. |
checkout.expired | The session or linked purchase expired. |
refund.completed | The refund completed and was recorded. |
#Event payload
Every event has a stable id. Use it as a unique key before changing access, inventory, credits, or order state.
{
"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"
}
}
}#Verify the signature
Rantan-Signature contains t={unix timestamp},v1={HMAC-SHA256}. Compute the digest over the exact raw body as `${t}.${rawBody}`, reject timestamps older than five minutes, and compare in constant time.
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();
}#Retries and delivery history
Return any 2xx status within 10 seconds. Redirects are not followed. Failed deliveries retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 24 hours, and 48 hours, then become failed after eight attempts.
#Manual retry
Inspect up to 100 recent deliveries and manually replay a delivered or failed event. The event id stays the same, so your handler must remain idempotent.
curl -X POST \
https://app.rantan.xyz/api/v2/webhooks/deliveries/$DELIVERY_ID/retry/ \
-H "Authorization: Bearer $RANTAN_SELLER_JWT"#Webhook endpoints
/api/v2/webhooks/endpoints/Create an endpoint and receive its initial signing_secret.
/api/v2/webhooks/endpoints/List the seller’s endpoints.
/api/v2/webhooks/endpoints/{endpointId}/Update the URL, subscriptions, or active state.
/api/v2/webhooks/endpoints/{endpointId}/Read one endpoint configuration.
/api/v2/webhooks/endpoints/{endpointId}/Disable an endpoint and stop future deliveries.
/api/v2/webhooks/endpoints/{endpointId}/rotate-secret/Invalidate the old secret and receive a new signing_secret.
/api/v2/webhooks/deliveries/List delivery status and response history.
/api/v2/webhooks/deliveries/{deliveryId}/retry/Replay a delivery with the same event id.