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-timestampandwebhook-signature: v1,<base64>, an HMAC-SHA256 ofid.timestamp.bodywith your endpoint'swhsec_secret. - Retries. Answer 2xx within 10 seconds; anything else is retried after 1m, 5m, 30m, 2h and 12h.
- Test button. Send a
pingto your endpoint and see what it answered.
End to end
Track a box, get the change, check the signature.
- 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
Receive
container.changedWhen 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
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.