Skip to main content
The maker feed tells you what to quote. Webhooks tell you what happened to your swaps.

Register

Response (201): { "webhook": { id, url, events, enabled }, "secret": "whsec_…" }. The secret is shown once. Store it; you need it to verify deliveries.
  • url must be https. 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 as lastStatus).
  • Omit events, or send an empty list, to receive all events.
Other calls: 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 a POST with a JSON body and these headers: Delivery does not follow redirects and times out after 10 seconds.

Verify the signature

The signature is HMAC-SHA256(secret, "<timestamp>.<body>"), hex-encoded, where:
  • secret is the full whsec_… string from registration, used as the HMAC key as-is;
  • timestamp is the x-hashlock-timestamp header;
  • body is the raw request body, byte for byte. Do not re-serialize parsed JSON.
The 300-second window above is an example value for your receiver, not a server setting.

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.