Webhooks
Subscribe to events: request completed, error, budget alert, key created, fine-tuning job done, and more.
Overview
Webhooks deliver events from AllRoutes to your HTTP endpoint as they happen. Use them to:
- Get alerted when a key approaches its monthly budget
- Trigger downstream pipelines when a fine-tuning job completes
- Log every error to your incident system
- Update internal dashboards on every chat completion
Webhooks differ from Broadcast in that they're event-driven (not high-throughput trace streaming) and signed for tamper-evidence. For per-request tracing at scale, use Broadcast; for selective events, use Webhooks.
Event Types
| Event | Fires When |
|---|---|
completion.success | A chat completion succeeds (sampled) |
completion.error | A chat completion fails (4xx or 5xx) |
budget.threshold | A key crosses 50%, 80%, or 100% of its daily/monthly budget |
budget.exhausted | A key hits its budget cap (402) |
key.created / key.deleted / key.updated | API key lifecycle |
provider_key.invalid | A BYOK provider key fails validation |
endpoint.unhealthy | A self-hosted endpoint fails health checks |
fine_tuning.job.succeeded / .failed | Fine-tuning job lifecycle |
guardrail.triggered | A guardrail (PII, injection, toxicity) blocked a request |
cache.purged | The cache was manually purged |
Subscribing
POST https://api.allroutes.ai/v1/webhooks
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint to deliver events |
events | array | Yes | Event types to subscribe to (["*"] for all) |
secret | string | No | Auto-generated if omitted; used to sign payloads |
description | string | No | Human-readable label |
enabled | boolean | No | Default: true |
Example
curl -X POST https://api.allroutes.ai/v1/webhooks \
-H "Authorization: Bearer allroutes_mgmt_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/api/allroutes-webhooks",
"events": ["budget.threshold", "budget.exhausted", "completion.error"],
"description": "Production budget and error alerts"
}'
Response
{
"id": "whk_abc123",
"url": "https://your-app.com/api/allroutes-webhooks",
"events": ["budget.threshold", "budget.exhausted", "completion.error"],
"secret": "whsec_a1b2c3d4...",
"enabled": true,
"created_at": "2026-04-05T10:00:00Z"
}
Save the secret -- it's required to verify incoming payloads and is only returned at creation time.
Payload Format
Every webhook delivery is an HTTPS POST with a JSON body and a few specific headers:
POST /api/allroutes-webhooks HTTP/1.1
Content-Type: application/json
AllRoutes-Webhook-Id: evt_xyz789
AllRoutes-Webhook-Event: budget.threshold
AllRoutes-Webhook-Timestamp: 1714000000
AllRoutes-Webhook-Signature: t=1714000000,v1=5257a869...
{
"id": "evt_xyz789",
"type": "budget.threshold",
"created_at": "2026-04-05T14:30:00Z",
"data": {
"key_id": "key_abc123",
"key_name": "Production Backend",
"threshold_percent": 80,
"period": "monthly",
"budget_usd": 1000.00,
"spent_usd": 800.43,
"period_end": "2026-05-01T00:00:00Z"
}
}
Verifying Signatures
The AllRoutes-Webhook-Signature header is HMAC-SHA256 over <timestamp>.<body> using your webhook secret. Verify before trusting the payload.
Node.js
import crypto from "crypto";
function verifyWebhook(payload: string, headers: Record<string, string>, secret: string): boolean {
const sig = headers["allroutes-webhook-signature"];
const ts = headers["allroutes-webhook-timestamp"];
if (!sig || !ts) return false;
// Reject events older than 5 minutes (replay protection)
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${payload}`)
.digest("hex");
// Parse "t=...,v1=<hex>"
const v1 = sig.split(",").find((p) => p.startsWith("v1="))?.slice(3);
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1 ?? ""));
}
Python
import hmac
import hashlib
import time
def verify_webhook(payload: bytes, headers: dict, secret: str) -> bool:
sig = headers.get("allroutes-webhook-signature", "")
ts = headers.get("allroutes-webhook-timestamp", "")
if not sig or not ts:
return False
# Reject events older than 5 minutes
if abs(time.time() - int(ts)) > 300:
return False
expected = hmac.new(
secret.encode(),
f"{ts}.".encode() + payload,
hashlib.sha256,
).hexdigest()
v1 = next((p[3:] for p in sig.split(",") if p.startswith("v1=")), "")
return hmac.compare_digest(expected, v1)
Delivery and Retries
- Timeout -- 10 seconds per delivery attempt
- Success -- any 2xx response
- Retries -- exponential backoff (10s, 1m, 10m, 1h, 6h, 24h) on 5xx, timeout, or connection errors. After 6 failed attempts (3 days total), the event is dropped and the webhook is auto-disabled
- Concurrency -- events for the same webhook are delivered in order; events for different webhooks are parallel
A 4xx response (other than 408/429) is treated as a permanent failure and not retried -- this lets you intentionally reject events you don't care about.
Idempotency
Every event has a stable id (e.g., evt_xyz789). The same event may be delivered more than once if your endpoint times out and the retry succeeds. Deduplicate using id on receipt.
Testing
Trigger a test event from the dashboard, or via the API:
curl -X POST https://api.allroutes.ai/v1/webhooks/whk_abc123/test \
-H "Authorization: Bearer allroutes_mgmt_..." \
-d '{"type": "budget.threshold"}'
The test sends a synthetic payload with data.test: true so you can wire up your handler without consuming real budget.
Managing Subscriptions
# List
curl https://api.allroutes.ai/v1/webhooks -H "Authorization: Bearer allroutes_mgmt_..."
# Update
curl -X PATCH https://api.allroutes.ai/v1/webhooks/whk_abc123 \
-H "Authorization: Bearer allroutes_mgmt_..." \
-d '{"events": ["completion.error"]}'
# Disable
curl -X PATCH https://api.allroutes.ai/v1/webhooks/whk_abc123 \
-H "Authorization: Bearer allroutes_mgmt_..." \
-d '{"enabled": false}'
# Delete
curl -X DELETE https://api.allroutes.ai/v1/webhooks/whk_abc123 \
-H "Authorization: Bearer allroutes_mgmt_..."
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Webhook auto-disabled | 6 consecutive failures | Re-enable in dashboard, fix endpoint, send test event |
| Signature verification fails | Wrong secret or body parsed before hashing | Hash the raw bytes, not the parsed JSON |
| Events out of order | Endpoint returns slow on some events | Use a queue between webhook receiver and processing logic |
| Test event has no effect | Test events have data.test: true | Filter test events in your handler if you don't want them |
See Also
- Broadcast -- continuous high-throughput tracing
- Analytics -- the queryable backing store for event metrics
- Guardrails -- subscribe to
guardrail.triggeredfor security events - Fine-Tuning -- pair with
fine_tuning.job.succeededfor pipelines