Webhooks
OpenTrack offers the ability for developers to subscribe to a variety of events from our system. When these events occur, we will ping the server defined at the configured webhook URL with the relevant data, allowing you to receive low latency updates for important events generated by the OpenTrack system.
See how to register and consume webhooks
Webhook Delivery Envelope
All webhook events are delivered as an HTTP POST request to your configured URL with the following envelope:
{
"event": "<event-type>",
"data": { },
"changes": { },
"deliveryAttempt": 1,
"pendingRetries": 4,
"sentAt": "2025-07-19T17:00:00.654Z"
}event: The event type string (e.g.container.status.updated)data: The event payload. For most container events this is the full container snapshot (same asGET /v1/container). Some events use a specialized payload; see individual event docs.changes: An object showing what changed, with{ previous, current }pairs for each modified field. Not present on all events.deliveryAttempt: Current delivery attempt number (1-based)pendingRetries: Number of remaining retry attempts (up to 5 total)sentAt: ISO-8601 timestamp of when the webhook was sent
If a secret is configured on the webhook, the payload is signed and the signature is included in the X-OpenTrack-Signature header.
Idempotent Consumption
Webhook delivery is at-least-once. Your endpoint must safely handle receiving the same event more than once without producing duplicate side effects (such as sending duplicate emails or creating duplicate records).
Duplicate deliveries can occur when:
- Retries: Your server returns a non-2xx response or times out, and we retry (up to 5 attempts).
- Event replays: You use
GET /v1/eventsto play back historical events and re-ingest them.
Recommended: design for state convergence. Rather than triggering actions on every event, update your local record to reflect the current state from the payload and only perform side effects (emails, alerts) when the state actually changes. For example, if you receive a DISCHARGED → AVAILABLE status change but the container is already AVAILABLE in your system, skip processing. This makes your handler naturally idempotent.
Alternative: deduplication keys. If state-based processing is not practical, build a key from data.containerId + data.masterBill.number (or data.masterBillNumber for shipment events) combined with the event type and changes, and skip events you have already processed.
Default Events
When registering a webhook without specifying events, these are enabled by default:
container.status.updatedcontainer.itinerary.updatedcontainer.location.updatedcontainer.holds.updatedcontainer.demurrage.updated
Event Catalog
| Event Name | Related Feature | Description |
|---|---|---|
| container.status.updated | Container Visibility | A container has transitioned to a new milestone status |
| container.itinerary.updated | Container Visibility | The itinerary details of a container voyage have changed |
| container.location.updated | Container Visibility | The container's real-time location has been updated |
| container.history.updated | Container Visibility | New history entries have been added to the container's event timeline |
| container.holds.updated | Container Visibility | Holds or customs statuses on the container have changed |
| container.demurrage.updated | Container Visibility | Demurrage-related dates or charges have been updated |
| container.exceptions.updated | Container Visibility | An exception has been detected on the container |
| container.tracking.stopped | Container Visibility | Tracking for this container has been stopped |
| container.tracking.failed | Container Visibility | The system was unable to track this container |
| shipment.itinerary.updated | Shipment Visibility | The itinerary of a shipment has been updated |
| shipment.tracking.failed | Shipment Visibility | The system was unable to track this shipment |
| customs.entry.status.updated | Customs Visibility | A customs entry status has been updated |
