DEV Community

Keshav Sharma
Keshav Sharma

Posted on Originally published at settlematic.com

Crypto billing webhooks: sign them or don't bother

A billing webhook that is not signed is just a POST anyone can fire. That is not an integration. That is an open invoice-status endpoint.

What has to be true

  • HMAC (or equivalent) over the raw body. Secret known only to you and the merchant.
  • Timestamp in the signed payload. Reject stale deliveries.
  • Idempotency key. Chain reorgs and retries will hit you twice.
  • Verify against the chain (or a second indexer), not against "we already emailed paid."

Unsigned JSON that says { "invoiceId": "…", "status": "paid" } is how you get fake settlements.

Dual-source, then notify

Watch the deposit address on-chain. Confirm with a second source if you can. Only then POST. The webhook is a convenience. The ledger is the chain.

Retries: exponential backoff. 4xx that is not 429 should not loop forever. 5xx and timeouts should.

Longer write-up: Webhook patterns for crypto billing.

I work on this at Settlematic. Collect is live. Gateway is sandbox.

Top comments (1)

Collapse
 
raknaos profile image
Baptiste Le Bouquin

You're right that the ledger is the chain. I run a small fleet of automation agents on a VPS, and the worst production bug I've had in this category was trusting a delivery that looked canonical: the sender's own retry re-fired an old payload after a timeout on our side, and downstream state treated it as a new settlement. Idempotency keys fixed it, but only because we keyed on a sequence number too, not just the event ID — retries and distinct events don't always differ by ID alone.

A question on the dual-source approach: how do you pick confirmation depth when watching the deposit address? On fast chains it's tempting to notify on first inclusion, but a reorg after you've already POSTed paid means you need a compensating webhook — and most merchant handlers are written assuming status transitions only go forward.

On retries: we treat 403 like 5xx, not like other 4xx. A surprising share of 403s in practice turn out to be WAFs or proxies in front of the receiver choking on a retry burst, not the receiver rejecting the payload. Curious whether you've seen the same, or whether for billing specifically you'd rather fail loudly than keep looping.

Last thing worth saying out loud: unsigned webhooks fail silently in a way signed ones don't. A tampered payload with HMAC at least errors; with unsigned JSON, the first symptom is a finance report that doesn't match the chain weeks later. That failure-mode asymmetry alone justifies the implementation cost.