For developers

Container tracking API and webhooks. Same data, right inside your system.

Start tracking a container with one call. DwellZero checks it with the terminal several times a day, more often as its Last Free Day gets close, and tells your system with a signed webhook when anything changes.

# start tracking a container
curl -X POST https://api.dwellzero.com/tracking \
  -H "Authorization: Bearer $DWELLZERO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"terminalCode":"NWK-MAH","containerNumber":"MSCU1234566"}'
# when the box changes, to your endpointPOST https://your-app.example/dwellzero/webhookwebhook-id: evt_8sKq2mXbV3rT0yPzL1nW4eHdwebhook-timestamp: 1793037605webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= { "event": "container.changed",  "container": { "containerNumber": "MSCU1234566",    "terminal": "Maher Terminal", "lastFreeDay": "2026-10-30", … },  "changes": [{ "field": "available",                "oldValue": false, "newValue": true }] } # your server answers200 OK

Who it's for

Building a TMS, an ERP or your own app?

A TMS or dispatch tool

Show the LFD, holds and availability next to each load, and plan pickups before free time runs out.

An ERP or visibility platform

Keep container status in the system your team already uses, updated by webhooks instead of manual checks.

Your own app or scripts

Add containers from arrival notices or bookings and post changes to Slack, email or your database.

What you get

A REST API and signed webhooks.

REST API

JSON over https at https://api.dwellzero.com, with an API key in the Authorization header.

  • POST/trackingStart tracking a container (terminal code, container number, alert emails, bill of lading)
  • POST/tracking/bulkUp to 500 containers in one request
  • GET/trackingYour tracked containers with their latest status
  • GET/tracking/:trackingIdOne container: LFD, holds, availability, discharge, gate-out, vessel
  • PATCH/tracking/:trackingId/notifyChange the alert emails (up to 10)
  • PATCH/tracking/:trackingId/bill-of-ladingSet or clear the bill of lading (CSX ramps need it for the LFD)
  • DELETE/tracking/:trackingIdStop tracking
  • GET/terminalsEvery terminal code, no key needed

Signed webhooks

Up to 5 endpoints per account, each with the events it wants. They cover every container on the account.

  • container.tracking_startedDwellZero found the container at the terminal, with the LFD when it is known.
  • container.changedAvailability, holds, the Last Free Day, terminal status or gate-out changed. Lists each change.
  • container.lfd_reminderThe Last Free Day is in 2 days, tomorrow or today.
  • container.lfd_passedDaily while the container is past its Last Free Day and still at the terminal.
  • container.tracking_stoppedTracking stopped: the container never showed up, or the terminal kept failing.
  • Standard Webhooks signatures. webhook-id, webhook-timestamp and webhook-signature: v1,<base64>, an HMAC-SHA256 of id.timestamp.body with your endpoint's whsec_ secret.
  • Retries. Answer 2xx within 10 seconds; anything else is retried after 1m, 5m, 30m, 2h and 12h.
  • Test button. Send a ping to your endpoint and see what it answered.

End to end

Track a box, get the change, check the signature.

  1. 1

    Start tracking

    One call per container, with the terminal code from GET /terminals.

    curl -X POST https://api.dwellzero.com/tracking \
      -H "Authorization: Bearer $DWELLZERO_KEY" \
      -H "Content-Type: application/json" \
      -d '{"terminalCode":"NWK-MAH","containerNumber":"MSCU1234566","notifyEmails":["dispatch@example.com"]}'
    
    # 201 Created
    { "success": true, "trackingId": "tracking-uuid", "status": "syncing", "isNew": true, ... }
  2. 2

    Receive container.changed

    When the box becomes available, DwellZero POSTs the event to your endpoint: the container as it is now, and what changed.

    {
      "id": "evt_8sKq2mXbV3rT0yPzL1nW4eHd",
      "event": "container.changed",
      "createdAt": "2026-10-26T18:00:05.000Z",
      "trackingId": "tracking-uuid",
      "container": {
        "containerNumber": "MSCU1234566",
        "terminalCode": "NWK-MAH",
        "terminal": "Maher Terminal",
        "location": "New Jersey",
        "status": "active",
        "lifecycle": "AVAILABLE",
        "lastFreeDay": "2026-10-30",
        "available": true,
        "transitState": "IN YARD",
        "freightReleased": true,
        "customsReleased": true,
        "hasHolds": false,
        "holds": [],
        "dischargedAt": "2026-10-25T10:00:00Z",
        "gateOutAt": null,
        "vessel": { "name": "OCEAN ANNA", "voyage": "123W" },
        "line": { "name": "Ocean Line" },
        "lastCheckedAt": "2026-10-26T18:00:00Z",
        "lastChangeAt": "2026-10-26T18:00:00Z",
        "billOfLading": "MEDU1234567"
      },
      "changes": [{ "field": "available", "oldValue": false, "newValue": true }],
      "shareUrl": "https://app.dwellzero.com/c/<token>"
    }
  3. 3

    Verify the signature

    Check it on the raw body before parsing the JSON. Node.js, no libraries:

    const crypto = require('crypto');
    
    // Express: app.post('/dwellzero/webhook', express.raw({ type: 'application/json' }), handler)
    function verifyDwellZeroWebhook(rawBody, headers, secret) {
      const id = headers['webhook-id'];
      const timestamp = headers['webhook-timestamp'];
      const signatures = headers['webhook-signature'] || '';
      const ts = Number(timestamp);
      if (!id || !timestamp || !Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false; // missing, or older than 5 minutes
    
      const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
      const expected = crypto.createHmac('sha256', key)
        .update(`${id}.${timestamp}.${rawBody}`)
        .digest();
    
      return signatures.split(' ').some((entry) => {
        const [version, sig] = entry.split(',');
        const candidate = Buffer.from(sig || '', 'base64');
        return version === 'v1' && candidate.length === expected.length
          && crypto.timingSafeEqual(candidate, expected);
      });
    }

    A complete Express receiver is in the webhook guide.

Pricing

Same credits as the dashboard.

10 free containers a month, then 1 credit per container, charged once when tracking starts. Checks, API calls and webhooks are never charged. See pricing.

Contact us

Have a question or need help? Send us a message and we'll get back to you shortly.