Webhooks
Learn of every change on your server, signed.
Add an endpoint (merchant portal → Developers → Webhooks, or
POST /v1/webhook_endpoints) and choose the events.
You can have up to 16 endpoints per
environment.
What you receive
A POST with a JSON body:
{
"id": "evt_…",
"object": "event",
"type": "payment.succeeded",
"environment": "test",
"created_at": "2026-11-08T12:00:00.000Z",
"data": { "object": { "id": "pay_…", "status": "SUCCEEDED" } }
}data.object is the resource as it was when the event happened. Headers:
| Header | |
|---|---|
Sitesway-Signature | The signature (below) |
Sitesway-Event-Id | The event id — the same on every retry |
Sitesway-Event-Type | The event type |
Sitesway-Delivery-Id | This delivery |
User-Agent | Sitesway-Webhooks/1.0 |
Answer with any 2xx within 10 seconds. Anything else — or no
answer — is retried after 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 2 hours, 4 hours, 8 hours, 16 hours
(10 attempts in all). The same event can arrive more
than once: use the event id to process it once.
Verify the signature
Sitesway-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>Compute HMAC-SHA256 with your endpoint's secret (whsec_…)
over <t>.<the raw body>, and compare it with a v1 value in constant time.
Refuse a t more than 5 minutes from
now. Use the raw body exactly as received — not re-serialized JSON.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header, rawBody, secret, now = Math.floor(Date.now() / 1000)) {
const parts = header.split(',').map((p) => p.trim().split('='));
const t = Number(parts.find(([k]) => k === 't')?.[1]);
if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return parts
.filter(([k]) => k === 'v1')
.some(([, v]) => v.length === expected.length && timingSafeEqual(Buffer.from(v), Buffer.from(expected)));
}During a secret rotation the header carries one v1 per valid secret: accept
the request when any of them matches the secret you hold. After a rotation the
old secret keeps verifying for 1 day
unless you choose otherwise (at most 7 days).