iDrive Shipping API Webhooks — API Guide
This guide is for developers integrating with the iDrive Shipping API outbound webhook system. It covers what the service does, how to authenticate, how to create and manage subscriptions, what event payloads look like, and how to build a reliable consumer.
1. Overview
The iDrive Shipping API can push real-time event notifications to
an HTTPS endpoint you control, instead of you having to poll our API for changes.
Today the platform supports one consumer-facing event:
| Event | Fires when... |
|---|---|
tracking.updated | A shipment's tracking status changes. |
You register one or more webhook subscriptions, each pointing at a URL and a
set of event types. Whenever a matching event occurs, the platform sends an HTTP
POST with a JSON body to every active, matching subscription.
Key properties of the system:
- At-least-once delivery with automatic retries and exponential backoff.
- HMAC-SHA256 signing (or HTTP Basic Auth, or a custom header) so you can
verify a delivery genuinely came from the iDrive Shipping API. - Per-tenant isolation — you can only see and manage subscriptions and
events for your own tenant. - Auto-disable protection — a subscription that fails consistently is
disabled automatically so a broken endpoint on your side doesn't generate
endless retries.
2. Base URL & environments
https://api.dev.idrivelogistics.com # development / integration testing
https://api.idrivelogistics.com # production
All webhook management endpoints live under /api/v1/webhooks and
/api/v1/webhookEvents.
3. Authentication
The webhook API uses the same Auth0-based bearer token authentication as the
rest of the iDrive Shipping API. Machine-to-machine (M2M) credentials are the expected
integration path for server-to-server use:
-
Get an API key. Ask your iDrive Logistics account contact (or an
admin on your tenant) to issue aclientId/clientSecretpair for your
tenant. Treat this pair like a username/password — do not embed it in
client-side code. You can also self-serve by logging into the iDrive TMS and navigating to Settings>>API Keys. -
Exchange it for an access token:
POST /api/v1/tokens/m2m Content-Type: application/json { "grantType": "clientCredentials", "clientId": "<your clientId>", "clientSecret": "<your clientSecret>" }Response:
{ "accessToken": "<JWT>", "tokenType": "Bearer", "expiresIn": 3600 } -
Send the token as a Bearer header on every request:
curl -X GET https://api.dev.idrivelogistics.com/api/v1/webhooks \ -H "Authorization: Bearer <accessToken>"
Tokens expire after 1 hour (expiresIn). Your integration is responsible
for requesting a new token before the current one expires — there is no
refresh endpoint for M2M tokens.
Required permissions
Webhook endpoints are gated by three scopes, assigned to your API key on the
tenant:
| Scope | Grants |
|---|---|
webhooks.read | List/get subscriptions and ingested events. |
webhooks.write | Create/update subscriptions (implies read). |
webhooks.owner | Full control, including delete (implies read+write). |
If your token lacks the required scope, the API returns 403.
4. Quickstart
Step 1 — Create a subscription.
curl -X POST https://api.dev.idrivelogistics.com/api/v1/webhooks \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"uri": "https://your-app.example.com/webhooks/idrive",
"email": "[email protected]",
"registeredEvents": ["tracking.updated"],
"secret": "your-hmac-secret"
}'You get back a 201 with the created subscription (its webhookId starts
with kwbhk_). Either secret or credentials is required at creation time
so that every delivery can be authenticated — see Section 6.
Step 2 — Build a receiving endpoint. It must:
- Accept
POSTover HTTPS. - Read the raw request body (needed for signature verification).
- Verify the
X-Hmac-Sha256header before trusting the payload. - Respond with a
2xxstatus within 500 ms, before doing any slow work.
Step 3 — Deduplicate by event ID. Every payload has a globally unique id
(also sent as the x-idrive-event-id header). Store processed IDs and skip
anything you've already handled — see
Delivery guarantees & idempotency.
Step 4 — Watch for auto-disable. If your endpoint fails repeatedly, the
subscription is disabled and the email on file is notified. Re-enable with
PATCH /api/v1/webhooks/{webhookId} and { "isActive": true }.
5. Managing subscriptions
All endpoints below require a Bearer token and are scoped to your tenant.
Create — POST /api/v1/webhooks
POST /api/v1/webhooksRequires webhooks.write.
| Field | Required | Notes |
|---|---|---|
uri | Yes | HTTPS callback URL. |
email | Yes | Notified if the subscription is auto-disabled. |
registeredEvents | Yes | Non-empty array of event types (see Section 7). |
isActive | No | Defaults to true. |
secret | No* | HMAC-SHA256 signing secret. Write-only, never returned. |
credentials | No* | HTTP Basic Auth { username, password }. Write-only. |
customHeader | No | Single custom header { key, value } sent with every delivery. Write-only. |
* At least one of secret or credentials is required — the platform will
not create an unauthenticated webhook.
Response: 201 with a webhook object (see the DTO shape below).
List — GET /api/v1/webhooks
GET /api/v1/webhooksRequires webhooks.read. Supports standard pagination query params
(pageSize, pageToken, includeTotal).
Get — GET /api/v1/webhooks/{webhookId}
GET /api/v1/webhooks/{webhookId}Requires webhooks.read. 404 if the ID doesn't exist or belongs to another
tenant.
Update — PATCH /api/v1/webhooks/{webhookId}
PATCH /api/v1/webhooks/{webhookId}Requires webhooks.write. All fields optional and partial. To rotate/clear a
secret, credentials, or custom header you must explicitly set
updateSecret / updateCredentials / updateCustomHeader to true — this
prevents an accidental omission from silently wiping a stored value. Flipping
isActive from false to true also resets the consecutive-failure counter.
Delete — DELETE /api/v1/webhooks/{webhookId}
DELETE /api/v1/webhooks/{webhookId}Requires webhooks.owner.
Webhook object (response shape)
{
"webhookId": "kwbhk_abc123",
"tenantId": "tenant_xyz",
"uri": "https://your-app.example.com/webhooks/idrive",
"email": "[email protected]",
"registeredEvents": ["tracking.updated"],
"isActive": true,
"expirationDate": "2026-11-27T00:00:00.000Z",
"consecutiveFailures": 0,
"hasSecret": true,
"hasCredentials": false,
"hasCustomHeader": false,
"createdDate": "2026-07-30T00:00:00.000Z",
"updatedDate": "2026-07-30T00:00:00.000Z"
}Sensitive fields are never echoed back — you get hasSecret /
hasCredentials / hasCustomHeader booleans instead of the actual values.
Expiration: subscriptions expire 120 days after creation
(expirationDate) unless renewed viaPATCH. Plan to refresh long-lived
subscriptions before they lapse.
Full field-level detail: Managing subscriptions.
6. Viewing delivery history
Requires webhooks.read.
GET /api/v1/webhookEvents— list events ingested for your tenant
(paginated, same query params as above).GET /api/v1/webhookEvents/{eventId}— fetch a single event by its
kwevt_...ID.
This lets you audit what was published and cross-check against what your
endpoint actually received.
7. Event types & payloads
Every delivery shares a common envelope:
{
"id": "kwevt_abc123",
"eventType": "tracking.updated",
"tenantId": "your-tenant-id",
"payloadType": "TrackerV1",
"createdDate": "2026-06-15T12:00:00.000Z",
"payload": { ... }
}payloadType is the versioning mechanism — the shape of payload is tied to
this field, not to eventType. Route your handler on payloadType so it
stays forward-compatible when a new version (e.g. TrackerV2) is introduced.
Currently active: tracking.updated events carry a TrackerV1 payload — the
full tracker object (status, carrier, service level, scan events, etc.).
Full field-by-field reference, enum values, and a complete example payload:
Event payload reference.
Webhook lifecycle events (
webhook.created,webhook.updated,
webhook.disabled,webhook.deleted) exist internally for audit purposes
only. They are not currently delivered to consumer endpoints — you can
ignore them when building your integration.
8. Securing your endpoint
Every delivery is authenticated using whichever mechanism(s) you configured on
the subscription:
-
HMAC-SHA256 (
secret) — the platform sends anX-Hmac-Sha256header:
a base64-encoded HMAC-SHA256 of the raw request body. Recompute it on your
side and compare before trusting the payload.import { createHmac } from "node:crypto"; function isValidSignature( rawBody: string, signature: string, secret: string ): boolean { const expected = createHmac("sha256", secret) .update(rawBody, "utf8") .digest("base64"); return expected === signature; }Verify against the raw body, before JSON parsing — most frameworks
reformat whitespace when re-serializing, which breaks signature comparison. -
HTTP Basic Auth (
credentials) — the platform sends a standard
Authorization: Basic ...header using the username/password you configured. -
Custom header (
customHeader) — a single arbitrary header/value pair
sent on every delivery, useful for routing or a lightweight shared secret at
your load balancer/gateway.
You can combine a signing secret with a custom header, but not more than one
custom header, and not more than one auth scheme (Basic vs. HMAC secret) —
credentials and secret are independent knobs and either or both may be
set.
9. Delivery guarantees & retries
-
At-least-once delivery. The same event may occasionally be delivered
more than once (transient network errors, service restarts). Your endpoint
must be idempotent. -
Dedupe on
id(equivalently, thex-idrive-event-idheader). Retain
processed IDs for at least 48 hours. -
Retry schedule on non-2xx or timeout:
Attempt Delay before attempt 1 — 2 ~30 s 3 ~60 s 4 ~120 s 5 ~240 s (capped at 600 s) -
Respond fast. Return any
2xxwithin 500 ms; anything slower is
treated as a failure. Enqueue and return immediately — don't do the real
work synchronously in the handler. -
Auto-disable. After enough consecutive failures the subscription is
disabled and the contactemailis notified. Re-enable with
PATCH .../webhooks/{webhookId}{ "isActive": true }, which also resets
the failure counter.
Full detail and a suggested dedupe implementation:
Delivery guarantees & idempotency.
10. Errors
All non-2xx responses from the webhook API share this shape:
{
"message": "Webhook not found.",
"statusCode": 404,
"errorCode": "WEBHOOK_NOT_FOUND",
"errors": [],
"correlationId": "c1a2b3c4-..."
}| Status | Meaning |
|---|---|
400 | Validation error — check the errors array for details. |
403 | Your token lacks the required scope for this action. |
404 | Resource doesn't exist, or belongs to a different tenant. |
500 | Internal error — retry with backoff; include correlationId if you contact support. |
11. Best practices checklist
- Store your
clientSecretand webhooksecretoutside of source control. - Verify
X-Hmac-Sha256(or Basic Auth) on every request before parsing the body. - Deduplicate on
id— never assume single delivery. - Return
2xximmediately; process asynchronously. - Handle unknown
payloadTypevalues gracefully (log and skip, don't error). - Monitor the notification
emailinbox for auto-disable alerts. - Track your subscription's
expirationDateand renew before it lapses. - Refresh your M2M access token before the 1-hour expiry.
12. FAQ
Can I register multiple URLs for the same event?
Yes — create multiple subscriptions with overlapping registeredEvents; each
matching event is delivered to every active subscription independently.
Can I subscribe to events across multiple tenants?
No. All webhook management and delivery is scoped to the tenant your access
token belongs to.
How do I test without a public HTTPS endpoint?
Use a tunneling tool (ngrok or similar) against the dev environment while
integrating, then point at your production HTTPS endpoint for real traffic.
Non-https schemes are rejected outside of local development.
What happens to events sent while my endpoint was down?
They retry on the schedule in Section 9.
Once retries are exhausted for an event, the platform does not resend that
specific event again — use GET /api/v1/webhookEvents to review what was
sent and reconcile via the main API if you find gaps.
Updated 14 days ago
