Skip to content

Webhooks

Webhooks push real-time notifications to your server when things happen in Chronary — events created, updated, deleted, or agents modified. No polling required.

  1. You register a webhook URL and choose which event types to listen for
  2. When a matching event occurs, Chronary sends an HTTP POST to your URL
  3. The request includes an HMAC-SHA256 signature for verification
  4. Failed deliveries are automatically retried with exponential backoff
Terminal window
curl -X POST https://api.chronary.ai/v1/webhooks \
-H "Authorization: Bearer chr_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/chronary",
"events": ["event.created", "event.updated", "event.deleted"]
}'
Event typeTrigger
event.createdA new event is added to any calendar
event.updatedAn event’s fields are modified
event.deletedAn event is deleted
agent.createdA new agent is created
agent.updatedAn agent’s fields are modified

Each delivery is an HTTP POST whose body is the raw event payload — there is no { id, type, timestamp, data } envelope. The event type travels in the X-Chronary-Event-Type header, the delivery ID in X-Delivery-Id, and the signing timestamp in X-Timestamp. For example, an event.created delivery body:

{
"calendar_id": "cal_x1y2z3",
"event": {
"id": "evt_m1n2o3",
"calendarId": "cal_x1y2z3",
"title": "Strategy sync with Acme Corp",
"startTime": "2026-04-07T14:00:00Z",
"endTime": "2026-04-07T14:30:00Z",
"status": "confirmed"
}
}

Each event type carries its own payload shape — see the event catalog in the API reference.

Every delivery includes an X-Signature header — sha256=<hex>, an HMAC-SHA256 of `${X-Timestamp}.${rawBody}` keyed with your webhook secret — plus an X-Timestamp header (Unix epoch seconds). Verify the signature against the raw request body before trusting the payload, and reject deliveries whose X-Timestamp is older than ~5 minutes to defend against replay. The SDKs do both for you:

import { verifySignature } from '@chronary/sdk';
// Pass the RAW body string and the request headers.
// Throws on a bad/missing signature or a stale timestamp.
app.post('/webhooks/chronary', async (req, res) => {
const rawBody = await req.text();
try {
await verifySignature(rawBody, req.headers, process.env.WEBHOOK_SECRET);
} catch {
return res.status(401).send('Invalid signature');
}
// ...handle the verified payload
res.status(200).end();
});

If your endpoint returns a non-2xx status or times out (30 second timeout), Chronary retries with exponential backoff:

AttemptDelay
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.

Terminal window
curl https://api.chronary.ai/v1/webhooks \
-H "Authorization: Bearer chr_sk_your_key_here"

Change the URL, event types, or active status:

Terminal window
curl -X PATCH https://api.chronary.ai/v1/webhooks/whk_a1b2c3 \
-H "Authorization: Bearer chr_sk_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.

Terminal window
curl -X DELETE https://api.chronary.ai/v1/webhooks/whk_a1b2c3 \
-H "Authorization: Bearer chr_sk_your_key_here"

Returns 204 No Content.

  • 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 X-Delivery-Id header to deduplicate, as retries reuse the same delivery ID