Quick start
1
Register an endpoint
Send a Response (the only time
POST /v1/webhook_endpoints with your HTTPS receiver URL. The response carries a one-time secret — store it; you cannot read it again.secret is non-null):2
Verify the signature on every delivery
Every delivery carries an
Aurous-Webhook-Signature header of the form t=<unix_sec>,v1=<hex>. Reconstruct the canonical signed payload (${t}.${raw_body}) and HMAC-SHA256 it with your stored secret. See the verifier examples.Aurous Labs uses Unix seconds (a 10-digit timestamp) for the t= parameter, matching the Stripe webhook convention.3
Return 2xx to acknowledge
Acknowledge within 10 seconds. Any non-2xx, timeout, connection refused, or TLS error counts as a failed delivery and triggers a retry on the exponential-backoff schedule.
Event taxonomy
Subscribe to a subset via theevents array, or pass ["*"] to subscribe to every type at create time (the wildcard is expanded server-side at create time — events added in a later API version do NOT auto-subscribe).
image.expired, video.expired, image.moderation_rejected, and video.moderation_rejected are reserved enum values that are not emitted today — see the Changelog for when each lights up.
Routing by API key
By default every active endpoint subscribed to an event receives it. You can instead bind an endpoint to a single API key so it receives only the events generated with that key — ideal for keeping environments separate (your staging key delivers to your staging receiver, your production key to your production receiver). Setapi_key_id to a key’s public id (key_…) on create or update:
- An event generated with key
Kis delivered to the endpoint(s) bound toK. - If no endpoint is bound to
K, the event falls back to your unscoped endpoints (api_key_id: null— the catch-all). - Events with no API key (dashboard activity,
usage.balance_low) go to unscoped endpoints only.
api_key_id: null on PATCH to unbind (the endpoint becomes a catch-all again); omit the field to leave the binding unchanged. If the bound key is deleted, the endpoint automatically reverts to a catch-all.
Finding a key id. Use the key_… public id — it is case-sensitive. Read your key ids from the API keys page in the dashboard, or programmatically from GET /v1/usage?group_by=api_key_id (each bucket is keyed by its api_key_id).
Payload shape
Every delivery body is a single JSON object — the AurousEvent envelope:synthetic: true appears on the envelope (never inside data) when the delivery was triggered via POST /v1/webhook_endpoints/:id/test. Receivers can filter test fires from production traffic with a single field check.
Headers
Every delivery carries the following request headers:Signature format
Aurous-Webhook-Signature: t=<unix_sec>,v1=<hex>
Where:
<unix_sec>is the moment we minted the signature (a 10-digit Unix timestamp in seconds — the Stripe convention).<hex>is the lowercase hex ofHMAC-SHA256(secret, "${t}.${raw_body}").
t value goes INSIDE the HMAC payload, so an attacker cannot replay a captured body with a tweaked timestamp.
We recommend rejecting any delivery whose t is older than 5 minutes — adjust the tolerance if your receiver uses a lossy queue.
Retries and dead-letter
Failed deliveries (non-2xx, timeout, connect refused, or TLS error) are retried on this schedule:
After attempt 5, the delivery is marked
is_terminal: true and the row is flagged with is_dead_letter: true. We stop retrying.
If consecutive_failures >= 20 AND there has been no successful delivery in the last 24 hours, we auto-disable the endpoint:
is_activeflips tofalse.- We email the team owner.
- Other active endpoints on the team receive a
webhook.endpoint_disabledevent so peer integrations can react.
PATCH /v1/webhook_endpoints/:id with { "is_active": true } once you’ve fixed the receiver. The counter resets to 0 on re-enable.
Receiver cookbook
The contract is symmetric: we sign with the active secret only; you verify against the active secret first, then the previous secret as a 24h fallback. This receiver-side fallback is what makes secret rotation zero-downtime.Node.js (Express)
Python (Flask)
Manual curl verification
Useful for one-off debugging: capture a delivery body + signature, then verify locally.Rotating the secret
HitPOST /v1/webhook_endpoints/:id/rotate_secret to mint a new plaintext. The response carries the new secret exactly once.
The contract during rotation:
- Sender (Aurous Labs): signs every new delivery with the active secret only.
- Receiver (you): for the next 24 hours, store the previous secret as
PREVand verify againstACTIVEfirst, then fall back toPREV(as shown in the cookbook).
PREV. We never sign with it again.
Test firing
HitPOST /v1/webhook_endpoints/:id/test with an event_type to enqueue a real, signed synthetic delivery. The envelope carries synthetic: true so receivers can filter test fires from production traffic.
webhooks_test, 30 / minute) so they never contend with normal webhook traffic.
Inspecting deliveries
Walk the per-attempt log viaGET /v1/webhook_endpoints/:id/deliveries:
null for transport errors), error class (http_4xx / http_5xx / timeout / connect_refused / tls_error / connect_error), and the first 1KB of the response body if any. Cursor-paged via ?starting_after=<dlv_id>.
