Asynchronous events you don't want to poll for.
HMAC-signed POSTs for batch completions, anomaly detections, and balance thresholds. At-least-once delivery with exponential retry.
Register an endpoint
- Open dashboard → Settings → Webhooks.
- Add your endpoint URL (must be HTTPS).
- Pick which event types to subscribe to (or all).
- Copy the generated webhook signing secret — stored once, shown once. Use it to verify signatures.
- Anoman fires a test event immediately so you can confirm your endpoint is reachable.
You can register up to 3 endpoints per customer. Multiple endpoints receive the same event in parallel — useful for separating production from a staging mirror.
What we fire today
| Event type | When |
|---|---|
batch.completed | A queued batch job finished. Payload includes cost + savings. |
batch.failed | A batch job exhausted retries and won't recover. |
batch.escalated | Job was promoted from batch to realtime to meet SLA. Higher cost. |
anomaly.detected | Z-score or ML model flagged unusual behavior. Severity ∈ {low, medium, high}. |
balance.threshold | Prepaid balance crossed a configured low-balance threshold. |
key.budget_warning | Per-key budget crossed 80% / 90% / 100%. |
guardrail.spike | Burst of blocks exceeded baseline — possible attack or misconfigured agent. |
Example bodies
Every webhook body is a single JSON object with these top-level keys: event_id, event_type, occurred_at, data, meta.
POST {your-url} — batch.completed
POST {your-url} — anomaly.detected
POST {your-url} — balance.threshold
Always verify before processing
Every webhook request carries two headers:
x-anoman-timestamp— Unix timestamp the event was dispatched.x-anoman-signature— hex-encoded HMAC-SHA256 of{timestamp}.{raw_body}using your signing secret.
Always: (1) check the timestamp is within 5 minutes (replay protection), (2) recompute the HMAC over the raw request body, (3) compare in constant time.
Python — verify signature (FastAPI)
TypeScript — verify (Express)
Node — minimal signature check
Don't parse before verifying. JSON parsing then re-serializing breaks the byte exactness the HMAC depends on. Verify against the raw body bytes; only parse after the signature checks out.
At-least-once
We treat any 2xx response as success. Anything else (including timeouts > 10s) triggers a retry on this schedule:
- Attempt 1: immediate
- Attempt 2: 30 seconds later
- Attempt 3: 5 minutes later
- Attempt 4: 1 hour later
- Attempt 5: 6 hours later (final)
After attempt 5 the event is moved to the failed-deliveries queue (visible in dashboard) and a separate webhook.delivery_exhausted notification is emailed to your account admin.
Each delivery attempt increments meta.delivery_attempt in the payload. Use it to log + debug. Because we retry on any non-2xx, your endpoint must be idempotent:
Python — idempotency via event_id
Replay + manual dispatch
- The dashboard webhook page has a “Send test event” button that dispatches a synthetic payload of each event type.
- The delivery log shows every attempt with status code + response body. Replay any individual attempt with one click.
- For local development, use a tunnel like
ngrokorcloudflared tunnelto exposelocalhost:3000over HTTPS. The signing secret stays the same.
Set up your first webhook.
Takes 60 seconds. Free test events to verify wiring.