Guide
Webhooks & Events
ORN API events and webhook delivery: the event catalogue, signed payloads, retries, self-service subscription management in the Developer Console, and how delivery is verified.
Events today
Consequential write operations record a domain event as part of the same transaction that changes the record — the outbox pattern, so an event is never recorded for a change that did not happen. Events are recorded per organization and delivered to webhook subscriptions that match the event type.
| Event type | Emitted by | Payload focus |
|---|---|---|
| journey.created | create_journey | The newly created journey record. |
| journey.updated | update_journey | The updated fields and the new state. |
| journey.activated | activate_journey | The activated journey and its route. |
| journey.cancelled | cancel_journey | The cancelled journey and the cancellation. |
| checkpoint.observation_recorded | submit_checkpoint_observation | The recorded observation and its checkpoint. |
| safety.event_recorded | submit_safety_event | The recorded safety event. |
Read operations and the route, intelligence and passenger analysis capabilities do not emit events — they observe records rather than change them.
Delivery
A delivery worker drains recorded events and POSTs each one to every active webhook subscription of the organization whose event list includes the event type. Delivery is at-least-once per subscription: your endpoint may receive the same event more than once, so treat event id as the deduplication key.
{
"id": "event uuid",
"type": "journey.activated",
"created_at": "2026-09-16T09:30:00.000Z",
"resource": { "type": "journey", "id": "journey uuid" },
"data": { "…": "the event payload" }
}The JSON body of every webhook delivery.
| Header | Value |
|---|---|
| content-type | application/json |
| user-agent | ORN-Webhooks/1.0 |
| x-orn-event-id | The event id — deduplicate on this. |
| x-orn-event-type | The event type, e.g. journey.activated. |
| x-orn-timestamp | ISO 8601 time of the delivery attempt. |
| x-orn-signature | HMAC-SHA256 hex digest of the raw body, keyed with your subscription secret. |
Verifying signatures
Each webhook subscription has a signing secret (prefix whsec_). Compute an HMAC-SHA256 over the exact raw request body and compare it with the x-orn-signature header before trusting a delivery.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyOrnWebhook(rawBody, signatureHeader, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signatureHeader ?? '', 'utf8');
return a.length === b.length && timingSafeEqual(a, b);
}- Verify against the raw body — re-serialising parsed JSON can change the bytes and break the signature.
- Compare with a constant-time equality check.
- A delivery succeeds when your endpoint responds with any HTTP 2xx within 10 seconds; anything else is treated as a failed attempt.
Retries
Failed deliveries retry with exponential backoff, up to 5 attempts per subscription: 30 seconds, 1 minute, 2 minutes, 4 minutes, then 8 minutes after the previous attempt. Every attempt is recorded individually with its response status and a response snippet, and an event whose attempts are all exhausted is closed without blocking anything else.
- Make your endpoint fast and idempotent: respond 2xx after persisting the event, deduplicating on the event id.
- Do not verify signatures inside long-running work — verify, persist, respond, then process.
Managing subscriptions
Webhook subscriptions are self-service in the Developer Console in your dashboard (Developer → API → Webhooks). A subscription belongs to one of your active applications, points at one https:// endpoint, and is subscribed to one or more event types from the catalogue above.
- Create a subscription by choosing the application, an https:// endpoint URL and the event types to receive. One application can have one subscription per endpoint — a duplicate endpoint is rejected.
- Your signing secret is shown exactly once, at creation and at rotation — copy and store it immediately; it cannot be read back afterwards.
- Update the endpoint URL or event selection at any time; updating does not change the secret.
- Rotate the secret whenever it may have been exposed: the previous secret stops working immediately and the new one is shown exactly once.
- Disable a subscription to pause deliveries without losing its configuration; enable it to resume. Disable rather than delete unless the endpoint is gone for good.
- Delete a subscription only when you are finished with the endpoint permanently — deletion is irreversible and removes its delivery history with it.
- Send a test delivery to confirm reachability and signature verification before wiring real events: it is signed and sent exactly like a real event, but is not a domain event and does not count against retry attempts. Test deliveries require an active subscription.
The Console also shows your most recent delivery attempts across all subscriptions — including retries — with the HTTP response status each attempt received, so a failing endpoint is visible without instrumenting your side. A disabled subscription never receives deliveries.