Webhooks
Swft signs every outbound webhook with the Standard Webhooks
spec — the same headers OpenAI, Anthropic, and Google use in 2026. If
your stack already verifies Standard Webhooks (webhook-id,
webhook-timestamp, webhook-signature), drop Swft into it
unchanged.
Events
Section titled “Events”| Event | Fires when |
|---|---|
order.completed | Stripe payment_intent.succeeded for a session-based order. |
order.failed | Payment failed OR pre-confirmation rejected. |
cart.abandoned | Recovery emails consider the cart abandoned. |
refund.issued | Stripe charge.refunded. |
draw.winner_selected | Lottery draw winners committed. |
subscription.created | Stripe customer.subscription.created for a session-driven sub. |
subscription.renewed | Recurring invoice paid (billing_reason='subscription_cycle'). |
subscription.payment_failed | Recurring invoice failed. |
subscription.cancelled | Stripe customer.subscription.deleted. |
Payload shape
Section titled “Payload shape”{ "event": "order.completed", "created_at": "2026-06-01T12:00:00Z", "swft_version": "2.0", "merchant_id": "mer_8a1d...", "extensions": { "booking": { ... } }, "data": { "order_id": "ord_...", "session_id": "sess_...", "customer": { ... }, "billing_address": { ... }, "shipping_address": { ... }, "items": [ ... ], "subtotal_pence": 2500, "tax_pence": 0, "shipping_pence": 0, "discount_pence": 0, "total_pence": 2500, "currency": "GBP", "payment_method": "card" }}extensions is present when the session was created with non-empty
extensions (excluding b2b, which is broken out into its own
data.b2b field for back-compat).
Verifying signatures
Section titled “Verifying signatures”import { verifyStandardWebhook } from '@swft-checkout/js/webhooks'
if (!verifyStandardWebhook({ id: req.headers.get('webhook-id')!, timestamp: req.headers.get('webhook-timestamp')!, body: rawBody, signatureHeader: req.headers.get('webhook-signature')!, candidateSecrets: [process.env.SWFT_WEBHOOK_SECRET!],})) return new Response('bad signature', { status: 401 })Pass MULTIPLE entries in candidateSecrets while you’re rotating
secrets — both the old and new will verify until you remove the old one.
Delivery log
Section titled “Delivery log”Every attempted delivery is persisted:
GET /v2/webhooks/deliveries?event=order.completed&status=failed&limit=50Inspect a single delivery (includes the partner’s response body):
GET /v2/webhooks/deliveries/{id}Replay
Section titled “Replay”POST /v2/webhooks/deliveries/{id}/replayIdempotency-Key: <uuid>Resets the attempt counter and re-fires immediately. Use this to recover after fixing a bug on your endpoint.
Retry policy
Section titled “Retry policy”Exponential backoff with ±20% jitter, 16 attempts spread across
roughly 3 days, then abandoned. Replay manually if you need to
retry an abandoned delivery.