Webhooks
Webhooks deliver events from ChronaPilot to your server. They're how you receive event.created, departure.updated, connection.disconnected, and dozens of other lifecycle signals.
Register an endpoint
curl https://api.chronapilot.com/v1/webhook_endpoints \ -X POST \ -H "Authorization: Bearer sk_live_…" \ -d '{ "url": "https://your-app.com/webhooks/chronapilot", "events": ["event.created", "event.updated", "departure.updated"] }'
The response includes a secret — store it. You'll never see it again.
Receiving deliveries
Each delivery is a POST to your URL with:
POST /webhooks/chronapilot HTTP/1.1
Chronapilot-Signature: t=1716247200,v1=5257a869e7ecebeda32afb5e26a52e9eda1a8b1...
Chronapilot-Event: whe_8x9y0z1a2b
Content-Type: application/json
{
"id": "whe_8x9y0z1a2b",
"type": "event.created",
"api_version": "2026-05-20",
"created": 1716247200,
"data": { "object": { … } },
"livemode": true
}Verify the signature
The Chronapilot-Signature header has two comma-separated fields:
- t — Unix timestamp (seconds) when the signature was generated.
- v1 — HMAC-SHA256 hex digest.
Compute the expected signature as:
signed_payload = t + "." + raw_request_body expected = HMAC_SHA256(secret, signed_payload).hex()
raw_request_body is the exact bytes you received — do not parse and re-serialize the JSON. Compare expected to v1 using a constant-time comparator. Then reject any delivery where now - t > 300 seconds (5-minute tolerance) to defeat replay attacks.
import { Webhook } from '@chronapilot/node'; app.post('/webhooks/chronapilot', express.raw({type: 'application/json'}), (req, res) => { try { const event = Webhook.constructEvent( req.body, req.headers['chronapilot-signature'], process.env.WEBHOOK_SECRET ); // handle event.type res.json({ received: true }); } catch (err) { res.status(400).send('Invalid signature'); } } );
Always use the raw request body (bytes), not the parsed JSON. Re-serialized JSON will not produce a matching signature.
Retries
Failed deliveries (non-2xx, or no response within 8s) are retried up to 24 times over 72 hours with exponential backoff. After that the endpoint is marked unhealthy and the delivery is logged but not retried.
You can disable an endpoint temporarily without deleting it; queued deliveries pause until you re-enable.
Ordering
ChronaPilot does not guarantee delivery order. Two events fired close together may arrive at your server in either order. Always handle events by their created timestamp, and treat each delivery as the latest state of that resource.
Replay
Every delivery is logged in the dashboard for 30 days. From the dashboard, you can:
- Inspect the request body and your response.
- Re-fire any past delivery to your endpoint or a different URL.
- Bulk-replay a window of deliveries (useful after a downstream outage).
Catalog
See the webhook event catalog for every event type and payload shape.