Register
201): { "webhook": { id, url, events, enabled }, "secret": "whsec_…" }. The secret is shown once. Store it; you need it to verify deliveries.
urlmust behttps. A non-public address literal is rejected at registration. A hostname that resolves into a private range registers, but every delivery to it fails (visible only aslastStatus).- Omit
events, or send an empty list, to receive all events.
GET /v1/webhooks (list, secret not shown), DELETE /v1/webhooks/{id}, POST /v1/webhooks/{id}/ping (sends a test ping delivery, returns { ok, deliveryId }).
Events
Events go to the webhooks of both parties of a swap.
Every body also has
type (the event name), deliveryId and createdAt. Payloads are minimal: fetch full detail from GET /v1/swaps/{id}.
Handle an unknown type by returning 2xx, not an error.
Delivery
Each delivery is aPOST with a JSON body and these headers:
Delivery does not follow redirects and times out after 10 seconds.
Verify the signature
The signature isHMAC-SHA256(secret, "<timestamp>.<body>"), hex-encoded, where:
secretis the fullwhsec_…string from registration, used as the HMAC key as-is;timestampis thex-hashlock-timestampheader;bodyis the raw request body, byte for byte. Do not re-serialize parsed JSON.
Retries are best-effort
A non-2xx or a timeout is retried after 15 s, 30 s, 60 s, 120 s and 240 s — 6 attempts over 7 m 45 s — and then dropped. There is no redelivery endpoint and no delivery log beyond lastStatus (and lastDeliveryAt) on GET /v1/webhooks.
If your receiver is down longer than that, you lose events. Treat GET /v1/swaps/{id} as the authoritative state and reconcile against it; do not treat webhooks as the record.