Webhooks
BLOX delivers events as HTTP POST requests to a URL you register. Every product uses the same envelope, the same signature scheme, and the same delivery policy — learn it once. Product pages list only which events fire and what is in data.
Register an endpoint
Register one endpoint per event type from the dashboard, Devtools → Webhooks.
| Type | Events |
|---|---|
CHECKOUT | Checkout events |
PAYOUT | Payout events |
WALLET | Wallet events |
Each type needs its matching product enabled on your account.
Your signing secret has no relationship to your API key. Rotating one does not affect the other.
Envelope
Every delivery, every product:
{
"eventId": "payout.updated:b0e6c2f4-…:SETTLED",
"event": "payout.updated",
"webhookType": "PAYOUT",
"timestamp": "2026-07-16T09:31:05.000Z",
"data": {}
}| Field | Description |
|---|---|
eventId | Unique per event. Key your idempotency on this |
event | Event name, e.g. payout.updated |
webhookType | CHECKOUT, PAYOUT, or WALLET |
timestamp | ISO 8601 |
data | Product-specific payload — see the product's events page |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-Blox-Timestamp | Unix seconds when the payload was signed |
X-Blox-Signature | sha256= + hex HMAC-SHA256 of "{timestamp}.{rawBody}" |
Verify the signature
HMAC-SHA256 over "{X-Blox-Timestamp}.{rawBody}", keyed with your endpoint's whsec_… secret.
Two things silently break this:
- Use the
X-Blox-Timestampheader, not thetimestampfield in the body. The header is Unix seconds; the body field is ISO 8601. They are not interchangeable, and the body one will never verify. - Verify over the raw body bytes, before JSON parsing. Re-serializing changes them and the signature will not match.
Compare in constant time, and reject deliveries whose X-Blox-Timestamp is more than 300 seconds from your server clock.
Delivery policy
| Value | |
|---|---|
| Timeout | 10 seconds |
| Retries | Up to 5 attempts, exponential backoff (~1m, 2m, 4m, 8m) |
| Redirects | Not followed — a 3xx counts as a failure |
| URL | Publicly reachable; must not resolve to private or local addresses |
Return 2xx quickly and do the work asynchronously. After the fifth failed attempt the event is dropped, so treat webhooks as the fast path and a status poll as your backstop.
Make handlers idempotent on eventId. Deliveries are not deduplicated on our side, so a retry of a delivery your server actually received will arrive again. Record the eventId and treat a repeat as a no-op.
URL requirements
- Publicly reachable from the internet
- HTTPS recommended (plain HTTP currently accepted)
- Must not resolve to private or local network addresses
- Redirects (3xx) are treated as failures
Testing
Use a tunnel (e.g. ngrok) for local endpoints, then trigger sandbox activity — create a checkout, send a payout, or deposit to a beneficiary address.