All articles

Journal

Crypto payment webhooks: verify the signature and handle retries

How to receive a paid webhook safely: check the X-CryBit-Signature header, ignore old requests, answer quickly and make handling idempotent.

  • API
  • Security
Crypto payment webhooks: verify the signature and handle retries

A webhook is a request that CryBit sends to your server when an invoice changes state. It is the most reliable way to learn that an order is paid. It is also a public URL, so anyone can send requests to it. Verify every request before you act on it.

What you receive

The request is a POST with a JSON body. The event name arrives in the X-CryBit-Event header, for example payment.paid. You choose the events you want in the merchant settings: paid, expired, refunded, cancelled. The webhook URL is set on the merchant, not in the invoice request.

Steps to verify a signed webhook: header, HMAC-SHA256, age check, 2xx answer

Check the signature

Every webhook has the header X-CryBit-Signature in the form t=<unix time>,v1=<hex>. To verify it, take t, join it with a dot and the raw request body, compute HMAC-SHA256 with your signing secret and compare the result with v1. Use a constant-time comparison. Reject the request if t is older than five minutes. The signing secret of each merchant is in the merchant settings, on the integration tab. Nobody can fake a paid status without it.

Compute the signature over the raw body exactly as received. If you parse and re-serialize the JSON first, the bytes change and the check fails.

Answer fast, retry safely

  • Answer with a 2xx status as soon as you have stored the event, and do the heavy work afterwards. CryBit waits about twelve seconds.
  • If you do not answer, CryBit retries with growing pauses: 15 seconds, 1 minute, 5, 15 and 30 minutes, then 2, 6 and 24 hours. An answer with a 4xx status, except 429, stops the retries.
  • Make handling idempotent: the same event can arrive more than once. Use your order_id or the invoice uuid as the key and ignore a second delivery.
  • Do not close an order from the browser widget event alone. The widget event is only for the user interface.

If you miss webhooks for any reason, ask for the invoice status with GET /api/v1/payments/{uuid}. The status is always the source of truth.

Accept crypto, receive USDT

Create a merchant, issue an invoice, try the sandbox. Live accepting opens after review.

More from the journal