Webhooks
Signed events for every payment and payout transition, with retries, a delivery log and replay.
Endpoint
POST /v1/webhook-endpoints { url, enabled_events? } returns the endpoint with its secret (whsec_…) once. Empty enabled_events receives everything. Up to 16 active endpoints; private, loopback and link-local URLs are refused.
Delivery
Each event is a POST with a JSON body and headers:
| Header | Value |
|---|---|
X-CryptoPay-Signature | v1=<hex>; HMAC-SHA256 of "{timestamp}.{raw body}" with the secret. Two v1= values for 24 h after a rotation (POST /v1/webhook-endpoints/:id/rotate-secret; pass { "revoke_previous": true } when the old secret leaked, or DELETE /v1/webhook-endpoints/:id/previous-secret to end an overlap early). |
X-CryptoPay-Timestamp | Unix seconds. Reject if older than 5 minutes. |
X-CryptoPay-Event-Id, X-CryptoPay-Event-Type, X-CryptoPay-Attempt | Dedupe key, type, attempt number. |
Body: { id: "evt_…", object: "event", type, mode, created_at, data: { payment_intent | payout } }. Endpoints belong to one mode: an endpoint created with a test key (or with the console in test mode) only receives mode: "test" events, a live endpoint only mode: "live". Still check mode in your handler before fulfilling an order, and store id to ignore a redelivery of an event you already processed. Respond 2xx within a few seconds; non-2xx responses are retried at 1 m, 5 m, 30 m, 2 h, 6 h, 12 h and 24 h, after which the delivery is failed and the endpoint is marked failing after 10 consecutive failures.
Events: payment.created, payment.processing, payment.confirmed, payment.underpaid, payment.overpaid, payment.expired, payment.failed, payment.held, payout.created, payout.confirmed, payout.failed, test.ping.
Verify
Node (@cryptopay/sdk):
const event = constructEvent({ rawBody, signature: headers["x-cryptopay-signature"], timestamp: headers["x-cryptopay-timestamp"], secret });Other languages
Dependency-free verifiers for Python and PHP, checked against the same fixture as the SDK, live in the repository under examples/webhooks/ together with minimal receivers.
Delivery log and replay
GET /v1/webhook-deliveries lists every attempt with the signed request body, the response status and the error; POST /v1/webhook-deliveries/:id/retry re-queues a finished delivery (fresh timestamp and signature). POST /v1/webhook-endpoints/:id/test sends a test.ping through the normal pipeline.