Webhooks
Subscribe your endpoint to plan, route, and stop lifecycle events. Routal pushes them as they happen.
Webhooks let Routal notify your systems when something changes — without you polling. Subscribe an HTTPS endpoint to one or more event types and Routal will POST a JSON envelope to it.
Webhooks are configured per project in the planner dashboard at
planner.routal.com/h/settings/developers/webhooks.
The API endpoints POST /v2/webhook, PUT /v2/webhook/{id}, DELETE /v2/webhook/{id} are dashboard-only today and are not part of the public REST surface.
Configuration
A webhook record has three fields you control:
| Field | Type | Description |
|---|---|---|
url | string | The HTTPS endpoint that will receive the POST. |
enabled_events | string[] | The event types this webhook should fire for (see Event catalog). |
enabled | boolean | Whether the webhook is active. Disabled webhooks receive nothing. |
A single project can have multiple webhooks, each subscribed to a different subset of events.
Delivery
When a subscribed event happens, Routal sends an HTTPS POST to your url with a JSON body. There are no custom Routal headers today — no X-Routal-Signature, no X-Routal-Event, no distinguishing User-Agent. Identify the call by the body's event_id and project_id, not by headers.
Envelope
{
"created_at": "2026-05-21T10:30:00.000Z",
"project_id": "4f75d991ac359f8c4c79d762",
"event_id": "routal.planner.2.plan.created",
"meta": { /* event metadata */ },
"data": { /* event payload */ }
}| Field | Type | Description |
|---|---|---|
created_at | ISO 8601 string | When the event was emitted server-side. |
project_id | string | The project the event belongs to. Useful if you proxy multiple projects through a single endpoint. |
event_id | string | The event type (despite the name). One of the values in the Event catalog. |
meta | object | Event-type-specific metadata. Schema varies by event_id. |
data | object | Event-type-specific payload — the resource that changed. Schema varies by event_id. |
event_id is the event type, not a unique delivery identifier. There is no per-delivery ID in the envelope today. Idempotency by event ID is not a viable pattern — see Idempotency below for what to do instead.
Event catalog
These are the eight event types currently emitted to webhooks:
event_id | When it fires |
|---|---|
routal.planner.2.plan.created | A plan was created. |
routal.planner.2.plan.updated | A plan was updated (metadata, status, or contents). |
routal.planner.2.plan.deleted | A plan was deleted. |
routal.planner.2.stop.created | A stop was created. |
routal.planner.2.stop.deleted | A stop was deleted. |
routal.planner.2.stop.reported | A stop received a report (proof of delivery, completion, or failure). |
routal.drivers.2.route.started | A driver opened the route in the driver app. |
routal.drivers.2.route.finished | A driver marked the route finished. |
More domain events fire inside Routal (route created/updated/deleted, optimization started/succeeded, vehicle CRUD, etc.) but are not delivered via webhooks today. If you need one of them for your integration, tell us.
Acknowledgement
Your endpoint should respond with any 2xx HTTP status code. Anything else (including timeouts) counts as a failure.
There is no documented timeout SLA for webhook responses today. Aim to respond in under a few seconds and offload heavy work to a background queue.
Failure handling and disable
Failures are tracked per webhook with a failure_count that resets on the first successful delivery.
| Threshold | What happens |
|---|---|
| 5 consecutive failures | Routal sends an email to the organization owner warning that the webhook is failing. |
| 50 consecutive failures | The webhook is automatically disabled (enabled is set to false) and another email is sent to the owner. Future events are not delivered until you re-enable it from the dashboard. |
There is no scheduled retry queue with documented intervals (e.g. 30s → 5m → 30m). The failure counter simply increments each time an event fires and the delivery fails; the next attempt happens whenever the next subscribed event fires.
You can manually replay failed deliveries from the webhook delivery log in the planner dashboard.
Idempotency
Because the envelope does not carry a unique per-delivery ID, you have two practical options:
- Dedup using fields inside
data. Most event payloads include the resourceidand a timestamp. For example, tworoutal.planner.2.plan.updatedevents for the same plan can be deduped bydata.id+data.updated_at. - Make your handler naturally idempotent. Instead of "INSERT a new row", "UPSERT on (project_id, resource_id, event_id)". The exact pattern depends on what your handler does.
A future iteration may add a unique delivery_id field; track the changelog for updates.
Signature verification
Routal does not sign webhook payloads today. There is no HMAC, no X-Routal-Signature header, no shared secret stored on the webhook record. If you need to authenticate that a request came from Routal, options today:
- IP allowlist. Restrict your endpoint to accept only requests from Routal's egress IPs (ask support for the current list).
- Secret in the URL. Subscribe a URL containing an opaque token, e.g.
https://your-host/webhooks/routal?token=.... Verify the token server-side. Lower-trust than HMAC but workable. - mTLS or VPC private link. If you have stronger requirements, talk to us.
Signed deliveries with HMAC-SHA256 are on the roadmap.
Local development
To test webhooks against a local server, use a tunnel like ngrok or Cloudflare Tunnel to expose your localhost URL. Subscribe the tunnel URL from the planner dashboard.
What's not supported today
- HMAC signing — see above.
- Per-delivery unique ID in the envelope — see Idempotency.
- Configurable retry intervals — only the failure-count → disable lifecycle.
- Public REST endpoints for webhook CRUD — manage from the dashboard.
- Webhook events for routes (
route.created/updated/deleted) and stops other than created/deleted/reported — not delivered today. - Webhook events for vehicles — not delivered today.
When any of the above ships, the changelog is the canonical place to find out.
