← Accept your first crypto payment

Webhooks

Authenticated payment events with durable at-least-once delivery.

Endpoints and keys

Register a public HTTPS URL with a valid certificate. Private destinations and redirects are rejected. Create and rotate responses include secret and keyId. Deliveries retain the encrypted key snapshot from fan-out, including retries and manual redelivery; retain old verification keys while those deliveries can be resent. Rotation changes future deliveries, not old snapshots.

Version 2 signing

Breaking change: body-only signatures are replaced by a versioned authenticated envelope. Preserve the exact raw UTF-8 body. Authenticate version, eventId, deliveryId, eventType, timestamp, keyId and attempt. X-Idempotency-Key must equal X-Event-Id. No version 1 fallback.

const h = req.headers;
const input = JSON.stringify([
  "vaultless.webhook", 2, h["x-event-id"], h["x-delivery-id"],
  h["x-event-type"], h["x-timestamp"], h["x-key-id"],
  h["x-attempt"], rawBody.toString("utf8")
]);
const expected = crypto.createHmac("sha256", secretForKeyId)
  .update(input, "utf8").digest();
// Require x-webhook-version === "2" and a 64-character hex signature.
// Compare equal-length buffers with crypto.timingSafeEqual.
// Require x-idempotency-key === x-event-id and timestamp within +/-300s.
// Only then parse the payload and use the authenticated eventId.

Receiver idempotency

Events include invoice.paid and treasury.credited. Retries and manual redelivery retain eventId and deliveryId; timestamp and attempt change. In your database, use a unique merchant/eventId key and commit deduplication together with fulfillment. A repeated valid event should return 2xx without another fulfillment. HTTP exactly-once is not promised: a sender crash after 2xx can cause another send.

Response and recovery

Return a small 2xx response within 5 seconds, including body completion. Responses are limited to 4096 bytes; redirects and oversized responses fail. Network errors, 408, 429 and 5xx retry with backoff, up to ten attempts per cycle. SQL recovery survives queue loss. Failed deliveries remain visible; manual redelivery starts a new cycle with the same identity. Revoking a compromised old key requires receiver rejection and endpoint deactivation, not rotation alone.