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:

EventFires when...
tracking.updatedA 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:

  1. Get an API key. Ask your iDrive Logistics account contact (or an
    admin on your tenant) to issue a clientId / clientSecret pair 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.

  2. 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
    }
  3. 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:

ScopeGrants
webhooks.readList/get subscriptions and ingested events.
webhooks.writeCreate/update subscriptions (implies read).
webhooks.ownerFull 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 POST over HTTPS.
  • Read the raw request body (needed for signature verification).
  • Verify the X-Hmac-Sha256 header before trusting the payload.
  • Respond with a 2xx status 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

Requires webhooks.write.

FieldRequiredNotes
uriYesHTTPS callback URL.
emailYesNotified if the subscription is auto-disabled.
registeredEventsYesNon-empty array of event types (see Section 7).
isActiveNoDefaults to true.
secretNo*HMAC-SHA256 signing secret. Write-only, never returned.
credentialsNo*HTTP Basic Auth { username, password }. Write-only.
customHeaderNoSingle 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

Requires webhooks.read. Supports standard pagination query params
(pageSize, pageToken, includeTotal).

Get — 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}

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}

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 via PATCH. 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 an X-Hmac-Sha256 header:
    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, the x-idrive-event-id header). Retain
    processed IDs for at least 48 hours.

  • Retry schedule on non-2xx or timeout:

    AttemptDelay before attempt
    1
    2~30 s
    3~60 s
    4~120 s
    5~240 s (capped at 600 s)
  • Respond fast. Return any 2xx within 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 contact email is 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-..."
}
StatusMeaning
400Validation error — check the errors array for details.
403Your token lacks the required scope for this action.
404Resource doesn't exist, or belongs to a different tenant.
500Internal error — retry with backoff; include correlationId if you contact support.

11. Best practices checklist

  • Store your clientSecret and webhook secret outside 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 2xx immediately; process asynchronously.
  • Handle unknown payloadType values gracefully (log and skip, don't error).
  • Monitor the notification email inbox for auto-disable alerts.
  • Track your subscription's expirationDate and 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.


Did this page help you?