Webhooks
Webhooks are optional and additive — they are never required for correctness. A webhook outage
has zero effect on order fulfillment, and GET /orders/{order_id} always reflects the true
current state independently of whether a webhook ever arrived.
Current status: management works today; automatic delivery does not yet. You can register, list, and revoke endpoints, and send a synchronous test ping, right now. Automatic delivery on real order-lifecycle events is not yet active — a registered endpoint will not receive a push notification when one of your orders actually changes state. Until automatic delivery is activated, poll
GET /orders/{order_id}(see Orders) to find out what happened to an order. This page documents the full delivery/retry/dead-letter design so you can integrate against it now and be ready the moment it's switched on — the Testing section below is unaffected and works today.
Setup
Register a callback URL and the events you want through Account → Developer in your Brenoz
account, or via the API itself once you already hold a key with the webhooks:manage scope:
POST /api/v1/reseller/webhooks
{
"url": "https://example.com/webhooks/brenoz",
"events": ["order.completed", "order.failed"]
}
The URL must be HTTPS and publicly reachable. It is re-validated on every delivery attempt, not only at registration — a URL that later re-resolves to a private address is rejected before any attempt, protecting against DNS-rebinding.
Events
| Event | Fires when |
|---|---|
order.created |
An order is successfully created |
order.processing |
The order moves from debited to actively fulfilling |
order.completed |
The order is fulfilled |
order.failed |
The order fails terminally |
order.refunded |
A failed order is refunded |
order.needs_attention |
The order is held for a supplier/manual-fulfillment reason |
Signing
Every delivery carries:
Brenoz-Signature: t=<unix_timestamp>,v1=<hex_hmac_sha256>
The signature is HMAC-SHA256(secret, "<timestamp>.<raw_body>"), keyed by the signing secret
shown exactly once at registration. To verify:
- Reconstruct
"<timestamp>.<raw_request_body>"exactly as received (no re-serialization). - Compute the HMAC-SHA256 with your stored secret.
- Compare it to the
v1value using a constant-time comparison. - Reject anything where
tis more than a few minutes old (replay protection) or whoseevent_idyou've already processed (at-least-once delivery — duplicates can happen).
Retries & dead-letter
Delivery is at-least-once with bounded exponential backoff. A delivery that exhausts every
attempt moves to a dead-letter state, visible on GET /webhooks/{id}/deliveries. A dead-lettered
delivery can be manually replayed exactly once per request — never a runaway retry storm.
Testing
POST /webhooks/{id}/test sends a synthetic test.ping immediately and synchronously — useful
to confirm your endpoint is reachable and your signature verification is correct before relying
on it for real order events. It never creates a delivery-history row.
Rate limits on test-ping and replay
Sending a test ping (available on both the public API and the Account → Developer dashboard) or manually replaying a dead-lettered delivery (public API only — there is no replay action on the dashboard today) each make one real outbound HTTP request to your endpoint on demand. Both actions share a single limit: 5 requests per 300 seconds, per account — not per endpoint, and shared across however many webhooks or surfaces you use, since a request through any of them is the same real-world outbound traffic. Exceeding it returns:
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>
{"error": {"code": "reseller_api.webhook_outbound_rate_limited", "message": "Too many test pings or replays for this endpoint. Please slow down.", ...}}
Wait at least Retry-After seconds before retrying. This limit is independent of, and in
addition to, the general per-key/per-account request budgets described in
Errors & Rate Limits — it exists specifically to bound how
much outbound traffic a test-ping/replay loop can generate, not to limit normal API usage.
Rotating or revoking a signing secret
There is no secret-rotation-with-overlap in v1 — only revoke-and-register. If a signing secret is exposed or you simply want a new one:
- Register a new webhook endpoint with the same URL and events.
- Update your receiver to verify against the new secret (shown once, at registration).
- Revoke the old endpoint.
Revocation takes effect immediately — a revoked endpoint's pending deliveries dead-letter
without ever attempting to send. Two things do not carry over to the new endpoint: its
id (any configuration you keyed on the old endpoint id must be updated) and its delivery
history (GET /webhooks/{id}/deliveries is scoped to one endpoint; the new endpoint starts
with none). To avoid a delivery gap, do steps 1–2 before step 3.