Sites WayDevelopers

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-SignatureThe signature (below)
Sitesway-Event-IdThe event id — the same on every retry
Sitesway-Event-TypeThe event type
Sitesway-Delivery-IdThis delivery
User-AgentSitesway-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).

On this page