Skip to content

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.

EventFires when
order.completedStripe payment_intent.succeeded for a session-based order.
order.failedPayment failed OR pre-confirmation rejected.
cart.abandonedRecovery emails consider the cart abandoned.
refund.issuedStripe charge.refunded.
draw.winner_selectedLottery draw winners committed.
subscription.createdStripe customer.subscription.created for a session-driven sub.
subscription.renewedRecurring invoice paid (billing_reason='subscription_cycle').
subscription.payment_failedRecurring invoice failed.
subscription.cancelledStripe customer.subscription.deleted.
{
"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).

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.

Every attempted delivery is persisted:

GET /v2/webhooks/deliveries?event=order.completed&status=failed&limit=50

Inspect a single delivery (includes the partner’s response body):

GET /v2/webhooks/deliveries/{id}
POST /v2/webhooks/deliveries/{id}/replay
Idempotency-Key: <uuid>

Resets the attempt counter and re-fires immediately. Use this to recover after fixing a bug on your endpoint.

Exponential backoff with ±20% jitter, 16 attempts spread across roughly 3 days, then abandoned. Replay manually if you need to retry an abandoned delivery.