Webhooks
Webhooks push real-time notifications to your server when things happen in Chronary — events created, updated, deleted, or agents modified. No polling required.
How it works
Section titled “How it works”- You register a webhook URL and choose which event types to listen for
- When a matching event occurs, Chronary sends an HTTP POST to your URL
- The request includes an HMAC-SHA256 signature for verification
- Failed deliveries are automatically retried with exponential backoff
Create a webhook
Section titled “Create a webhook”curl -X POST https://api.chronary.ai/v1/webhooks \ -H "Authorization: Bearer chr_sk_test_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhooks/chronary", "events": ["event.created", "event.updated", "event.deleted"] }'const response = await fetch('https://api.chronary.ai/v1/webhooks', { method: 'POST', headers: { 'Authorization': 'Bearer chr_sk_test_your_key_here', 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://your-server.com/webhooks/chronary', events: ['event.created', 'event.updated', 'event.deleted'], }),});const webhook = await response.json();console.log(webhook.id); // "whk_a1b2c3"console.log(webhook.secret); // Save this — used for HMAC verificationresponse = requests.post( 'https://api.chronary.ai/v1/webhooks', headers={'Authorization': 'Bearer chr_sk_test_your_key_here'}, json={ 'url': 'https://your-server.com/webhooks/chronary', 'events': ['event.created', 'event.updated', 'event.deleted'], },)webhook = response.json()print(webhook['secret']) # Save this for HMAC verificationEvent types
Section titled “Event types”| Event type | Trigger |
|---|---|
event.created | A new event is added to any calendar |
event.updated | An event’s fields are modified |
event.deleted | An event is deleted |
agent.created | A new agent is created |
agent.updated | An agent’s fields are modified |
Webhook payload
Section titled “Webhook payload”Each delivery is an HTTP POST with a JSON body:
{ "id": "whkevt_x1y2z3", "type": "event.created", "timestamp": "2026-04-07T14:00:00Z", "data": { "calendar_id": "cal_x1y2z3", "event": { "id": "evt_m1n2o3", "title": "Strategy sync with Acme Corp", "start_time": "2026-04-07T14:00:00Z", "end_time": "2026-04-07T14:30:00Z", "status": "confirmed" } }}Verifying signatures
Section titled “Verifying signatures”Every webhook request includes a X-Chronary-Signature header containing an HMAC-SHA256 signature of the request body.
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(body, signature, secret) { const expected = createHmac('sha256', secret) .update(body) .digest('hex'); return timingSafeEqual( Buffer.from(signature), Buffer.from(expected) );}
// In your request handler:const isValid = verifyWebhook( rawBody, req.headers['x-chronary-signature'], process.env.WEBHOOK_SECRET);if (!isValid) { return res.status(401).send('Invalid signature');}import hmacimport hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected)
# In your request handler:is_valid = verify_webhook( request.body, request.headers['X-Chronary-Signature'], os.environ['WEBHOOK_SECRET'],)if not is_valid: return Response('Invalid signature', status=401)Retry behavior
Section titled “Retry behavior”If your endpoint returns a non-2xx status or times out (30 second timeout), Chronary retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | ~1 minute |
| 2nd retry | ~5 minutes |
| 3rd retry | ~30 minutes |
| 4th retry | ~2 hours |
| 5th retry | ~8 hours |
After 5 failed retries, the delivery is marked as failed. The webhook remains active for future events.
List webhooks
Section titled “List webhooks”curl https://api.chronary.ai/v1/webhooks \ -H "Authorization: Bearer chr_sk_test_your_key_here"Update a webhook
Section titled “Update a webhook”Change the URL, event types, or active status:
curl -X PATCH https://api.chronary.ai/v1/webhooks/whk_a1b2c3 \ -H "Authorization: Bearer chr_sk_test_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "events": ["event.created", "event.updated"], "active": false }'Set active: false to temporarily pause deliveries without deleting the webhook.
Delete a webhook
Section titled “Delete a webhook”curl -X DELETE https://api.chronary.ai/v1/webhooks/whk_a1b2c3 \ -H "Authorization: Bearer chr_sk_test_your_key_here"Returns 204 No Content.
Best practices
Section titled “Best practices”- Always verify signatures — never trust a webhook payload without checking the HMAC
- Return 200 quickly — process the payload asynchronously if it takes more than a few seconds
- Handle duplicates — use the event
idto deduplicate, as retries may deliver the same event twice - Use test mode — test keys trigger webhooks with the same behavior as live mode