API integration guide

Receive delivery updates with RouteIQ webhooks

Send delivery status changes to your order system or agent without repeatedly polling every job. Subscribe an HTTPS endpoint to RouteIQ events and verify each request before processing it.

Updated October 12, 2026
RouteIQ retries failed deliveries, so the same event may arrive more than once. Design your receiver to tolerate duplicates and reconcile current job state before performing irreversible actions.

1. Create a subscription

Deploy an HTTPS endpoint reachable from RouteIQ. Localhost and private network addresses are not suitable delivery targets. Use an API key with webhook-management permission and generate a separate signing secret.

The example below requires curl, jq, and OpenSSL. Replace the site, API key, and endpoint. Store ROUTEIQ_WEBHOOK_SECRET securely on both the receiver and the subscription; it is different from your API key.

curl + jq
export ROUTEIQ_BASE_URL="https://YOUR-SITE.routeiq.app/api"
export ROUTEIQ_API_KEY="YOUR-API-KEY"
export WEBHOOK_URL="https://YOUR-DOMAIN.example/routeiq"
export ROUTEIQ_WEBHOOK_SECRET="$(openssl rand -hex 32)"

jq -n --arg url "$WEBHOOK_URL" --arg secret "$ROUTEIQ_WEBHOOK_SECRET" '{
  url: $url,
  events: ["job:create", "job:state_change", "stop:state_change"],
  secret_key: $secret,
  is_active: true
}' | curl --fail-with-body --silent --show-error \
  --request POST "$ROUTEIQ_BASE_URL/webhook" \
  -H "x-api-key: $ROUTEIQ_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @-

Save the returned subscription ID. Event names are lowercase entity:action strings. The payload contains event, a UTC RFC3339 timestamp, and data for the entity. A stop event describes a stop; use its job relationship when you need the parent job.

The API does not return the stored signing secret. When updating a subscription, omitting secret_key keeps it, supplying a new value replaces it, and an empty string removes it.

2. Verify the signature

X-Webhook-Signature is the lowercase hexadecimal HMAC-SHA256 of the exact request-body bytes, using your signing secret. Verify it with a constant-time comparison before parsing JSON. Reformatting JSON before verification changes the signature.

Download webhook-receiver.mjs. It uses Node.js 22 or later, rejects missing or invalid signatures, limits request bodies to 1 MiB, and returns a success status only after its handler finishes.

Shell
# Use the same secret you configured for the subscription.
node webhook-receiver.mjs

The executable example listens on 127.0.0.1:8787 and logs the event name for local testing. Before deploying, provide its exported createReceiver(secret, accept) function with a handler that commits to a durable queue or database. Put the receiver behind your HTTPS endpoint and size its body limit for your events.

3. Test a signed request locally

In a second terminal with the same ROUTEIQ_WEBHOOK_SECRET, send this synthetic event. The receiver should log the event name and the client should print 204. This tests your receiver, not delivery from RouteIQ.

Node.js
node --input-type=module <<'JS'
import { createHmac } from 'node:crypto';
const secret = process.env.ROUTEIQ_WEBHOOK_SECRET;
if (!secret) throw new Error('Set ROUTEIQ_WEBHOOK_SECRET');
const body = JSON.stringify({
  event: 'job:state_change',
  timestamp: new Date().toISOString(),
  data: { id: 'local-test-job' }
});
const signature = createHmac('sha256', secret).update(body).digest('hex');
const response = await fetch('http://127.0.0.1:8787/routeiq', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Webhook-Signature': signature
  },
  body
});
console.log(response.status);
JS

Repeat with an incorrect secret and expect 401. For an end-to-end test, deploy the receiver, create a subscription on your test site, change a test job’s state through its normal workflow, and confirm your application processes the delivered event.

4. Handle retries and recovery

Return a 2xx response after processing succeeds or durable storage commits. Timeouts, network errors, and non-success responses can trigger retries with backoff. If your queue is unavailable, return a failure rather than acknowledge an event you cannot retain.

Make downstream actions idempotent. The documented envelope does not include a unique delivery ID. A hash of the exact body can identify an identical retried payload, but it is not a general business-event identifier. Use durable business keys and current entity state to prevent duplicate notifications, charges, or assignments.

Do not assume event arrival order matches workflow order. When an update is missing or arrives late, read GET /job/{id} and reconcile your state.

A subscription can be deactivated when retries are exhausted, or immediately for 404, 410, or an unresolvable target. Monitor its status with GET /webhook?id=WEBHOOK-ID, repair the endpoint, and reactivate it with PUT /webhook?id=WEBHOOK-ID using the documented subscription fields and is_active: true. Do not assume reactivation replays missed events.

See the API reference for webhook operations and the dispatch guide for creating test work.