Docs menu
Webhooks
Subscribe an HTTPS endpoint and Darb POSTs a signed JSON delivery when work completes — so you can react to route and campaign events instead of polling. Every delivery is signed withStandard Webhooks headers you verify with an off-the-shelf library.
How delivery works
When an event fires, Darb sends an HTTP POST to your subscribed URL. The body is a JSON envelope — { type, timestamp, data } — and the request carries four Standard Webhooks headers (below). Retries reuse the same webhook-id, so treat deliveries as at-least-once and dedupe by webhook-id. Payloads are deliberately thin: resource IDs and a coarse status only — never a coordinate, phone number, or customer name (so the 30-day location purge can't be defeated through this side-channel).
Events
There are exactly three events. Each sample below shows the thin data payload for that event.
route.optimized
A route finished optimizing. status is the terminal route status (e.g.completed); route_id is the route you created.
{
"route_id": "r_0d3f8c21",
"status": "completed"
}location.collected
A campaign contact shared their location. Carries the campaign_id and thecontact_id only — never the collected coordinate or the phone number (fetch the resource if you need more).
{
"campaign_id": "c_9a1b7e40",
"contact_id": "ct_44f0a2d1"
}campaign.completed
A collection campaign reached a terminal state. route_id is present only when the completed campaign produced an optimized route.
{
"campaign_id": "c_9a1b7e40",
"status": "completed",
"route_id": "r_0d3f8c21"
}Delivery format
Each delivery body is the full envelope wrapping the data above. For example, aroute.optimized delivery:
{
"type": "route.optimized",
"timestamp": "2026-07-14T00:00:00+00:00",
"data": {
"route_id": "r_0d3f8c21",
"status": "completed"
}
}On the wire the body is serialized compact with sorted keys — that exact byte string is what the signature is computed over, so verify against the raw received body and never a re-serialized copy. The request carries these headers:
POST /your/webhook/endpoint HTTP/1.1
content-type: application/json
webhook-id: 550e8400-e29b-41d4-a716-446655440000
webhook-timestamp: 1783987200
webhook-signature: v1,K5oyc4Xr8m2xQ0y1x2s3D4e5F6g7H8i9J0kL1m2N3o=webhook-id— the event's UUID, stable across retries; dedupe on it.webhook-timestamp— epoch seconds; verification enforces a tolerance window.webhook-signature— the signature, formattedv1,<base64>.content-type— alwaysapplication/json.
The signing secret
Each subscription has its own signing secret in the form whsec_ followed by standard base64. It is shown once, when you create the subscription — store it securely; it is never shown again (regenerate it if lost). The secret is the HMAC key your verification library uses below.
Verifying a delivery
Verify every delivery before trusting it. Do not hand-roll an HMAC check — use the off-the-shelf standardwebhooks library. Pass the raw received body and the threewebhook-* headers; a bad signature (or a stale timestamp) raises, a good one returns cleanly. Python is shown first, Node second — the call is identical:Webhook(secret).verify(body, headers).
# pip install standardwebhooks
from standardwebhooks import Webhook
# secret: the whsec_... value shown ONCE when you created the subscription
wh = Webhook(secret)
# body: the EXACT received request body (raw bytes/string) — do NOT re-serialize it
wh.verify(body, {
"webhook-id": headers["webhook-id"],
"webhook-timestamp": headers["webhook-timestamp"],
"webhook-signature": headers["webhook-signature"],
})// npm i standardwebhooks
import { Webhook } from "standardwebhooks";
// secret: the whsec_... value shown ONCE when you created the subscription
const wh = new Webhook(secret);
// body: the EXACT received request body (raw string) — do NOT re-serialize it
wh.verify(body, {
"webhook-id": headers["webhook-id"],
"webhook-timestamp": headers["webhook-timestamp"],
"webhook-signature": headers["webhook-signature"],
});standardwebhooks ships verifiers for 11+ languages. Reference implementations and docs: standard-webhooks on GitHub.