Webhook Delivery
Webhooks push triggered alert events to your server as they fire. This is ideal for headless integrations, backend processing pipelines, and systems that cannot maintain a persistent WebSocket connection.
Webhook URL is configured once per user in your notification contacts — then enabled per subscription via the webhook channel toggle.
Setting up a webhook
Step 1: Configure your webhook URL in contacts. The server auto-generates an HMAC-SHA256 signing secret:
import { Chaos } from '@chaoslabs/ai-sdk';
const chaos = new Chaos({ apiKey: process.env.CHAOS_API_KEY! });
// Set webhook URL (once per user)
const contacts = await chaos.alerts.contacts.updateWebhook(
'https://your-server.com/webhooks/alerts'
);
// Save the auto-generated secret for signature verification
console.log(contacts.webhookSecret);
// Test reachability
const test = await chaos.alerts.contacts.testWebhook(
'https://your-server.com/webhooks/alerts'
);
console.log(test.success); // true
// Step 2: Enable webhook on a subscription
const sub = await chaos.alerts.subscriptions.create({
alert: {
alert_type: 'price_change',
asset: 'ETH',
target_price: 3000,
condition: 'above',
},
channels: { inApp: true, push: false, telegram: false, email: false, webhook: true },
});Webhook payload
When the alert triggers, Chaos AI sends a POST request to your URL with the triggered alert as a JSON body:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Chaos-Signature | HMAC-SHA256 hex digest (if secret is set) |
{
"id": "ta-60a5a3feae3b",
"userId": "user-123",
"subscriptionId": "ua-d7309261",
"alert": {
"alert_type": "price_change",
"asset": "ETH",
"target_price": 4000,
"condition": "above"
},
"chain": "ethereum",
"severity": "warning",
"title": "ETH price above $4,000",
"summary": "ETH has crossed your $4,000 threshold, currently at $4,125.",
"evidence": {
"dataPoints": [
{ "label": "Current Price", "value": "4125.50", "unit": "USD" },
{ "label": "Threshold", "value": "4000", "unit": "USD" }
],
"chartData": null,
"sourceLinks": null
},
"transactionContext": null,
"readState": "unread",
"archived": false,
"triggeredAt": "2026-03-17T14:30:00Z",
"createdAt": "2026-03-17T14:30:00Z"
}Signature verification
If you provided a secret in the webhook config, every delivery includes an X-Chaos-Signature header containing the HMAC-SHA256 hex digest of the request body.
Verify the signature on your server to ensure the request is authentic:
import { createHmac } from 'crypto';
function verifyWebhookSignature(
body: string,
signature: string,
secret: string
): boolean {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex');
return signature === expected;
}Express example
import express from 'express';
import { createHmac } from 'crypto';
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
app.post('/webhooks/alerts', express.text({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-chaos-signature'] as string;
const body = req.body as string;
// Verify HMAC signature
const expected = createHmac('sha256', WEBHOOK_SECRET)
.update(body)
.digest('hex');
if (signature !== expected) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
const alert = JSON.parse(body);
console.log(`[${alert.severity}] ${alert.title}`);
console.log(` ${alert.summary}`);
res.status(200).send('OK');
});
app.listen(3000);Retry policy
Webhook delivery flows through the SQS notification pipeline. Failed deliveries are automatically retried via SQS visibility timeout re-delivery (default: 5 minutes). The webhook request has a 10-second timeout.
Webhook notifications respect the same per-subscription cooldown as email and telegram — if the cooldown window is active, no external channel (email, telegram, webhook) will fire.
Managing webhooks
Webhook URL is managed in your user contacts, not per subscription. Toggle channels.webhook on each subscription to control which alerts deliver via webhook:
// Enable webhook on an existing subscription
await chaos.alerts.subscriptions.update('ua-abc123', {
channels: { inApp: true, push: false, telegram: true, email: true, webhook: true },
});
// Disable webhook on a subscription (other channels unaffected)
await chaos.alerts.subscriptions.update('ua-abc123', {
channels: { inApp: true, push: false, telegram: true, email: true, webhook: false },
});
// Update webhook URL
await chaos.alerts.contacts.updateWebhook('https://new-endpoint.com/webhooks/alerts');
// Remove webhook entirely
await chaos.alerts.contacts.removeWebhook();Webhook vs WebSocket
| Feature | Webhook | WebSocket |
|---|---|---|
| Connection model | Stateless — server pushes to your URL | Persistent — client maintains open connection |
| Latency | Slightly higher (HTTP round-trip) | Lower (already connected) |
| Reliability | Automatic retries on failure | Auto-reconnect on disconnect |
| Authentication | HMAC-SHA256 signature | API key in connection |
| Best for | Backend pipelines, serverless functions | Dashboards, bots, real-time UIs |
You can use both on the same subscription — the alert will be delivered through all configured channels.
Next steps
- Real-time Alerts — WebSocket streaming alternative
- Managing Subscriptions — Full subscription lifecycle
- Alerts API Reference — Full type definitions