1. Webhooks
  • Introduction
  • Getting Started
  • Authentication
  • Error Handling
  • Contact Support
  • Modaltrans API
    • Get Auth Token
      • Get Bearer Token
    • Accounting
      • Invoices
        • Retrieve an Invoice
        • List all Invoices
      • Invoice Items
        • List all Invoice Items
        • Deleted Invoice Items
      • Financials
        • Retrieve Financial Information
    • Customs
      • NCTS Declarations
        • List all NCTS Declarations
        • Retrieve an NCTS Declaration
        • Create an NCTS Declaration with XML Data
        • Update an NCTS Declaration
        • Download an NCTS Declaration
        • List Timelines of NCTS Declaration
        • List Messages of NCTS Decalaration
      • Customs Declarations
        • List all Customs Declarations
        • Retrieve a Custom Declaration
        • Create a Customs Declaration
        • Update a Custom Declaration
        • Delete a Custom Declaration
        • Retrieve a Declaration History
        • Retrieve an MRN Status of Declaration
      • Declaration Requests
        • List all Declaration Orders
        • Retrieve a Declaration Request
        • Create a Declaration Request
        • Update a Declaration Request
        • Delete a Declaration Request
      • Ens Declarations
        • Create an Ens Declaration
        • Download an Ens Declaration
    • Logistics
      • Bookings
        • Retrieve a Booking
        • List all Bookings
        • Create a Booking
        • Update a Booking
      • Locations
        • List all Locations
        • Retrieve a Location
      • Positions
        • Update a Transport
        • Get All Positions
        • Retrieve a Position
      • Tracking
        • Available Shipping Lines
        • Retrieve a Tracking Data
        • Subscribe to Tracking
      • Shipments
        • Retrieve a Shipment
        • List All Shipments
        • Retrieve Financials of Shipment
        • List Contacts of the Shipment
        • Create a Shipment
        • Update a Shipment
      • Tariffs
        • List All Tariffs
        • Retrieve a Tariff
        • Create a Tariff
        • Update a Tariff
    • Depot
      • Orders
        • Retrieve an Order
        • List all Orders
        • Create an Order
        • Update an Order
      • Products
        • Retrieve a Product
        • List all Products
        • Create a Product
        • Update a Product
        • List Product Stocks
      • Inventories
        • List all Inventories
    • Fleet Management
      • Drivers
        • Retrieve a Driver
        • Retrieve a Driver's Vehicle
        • List Driver Positions
        • Update a Driver
      • Vehicles
        • Retrieve a Vehicle
        • Update a Vehicle
      • Gps Service Controller
        • Create Gps Responses
      • Service Logs
        • List all Service Logs
        • Retrieve a Service Log
        • Create a Service Log
        • Delete a Service Log
        • Update a Service Log
      • Periodic Documents
        • Retrieve a Periodic Document
        • Update a Periodic Document
        • Create a Periodic Document
    • CRM
      • Companies
        • List all Companies
        • Retrieve a Company
        • Create a Company
        • Update a Company
        • Delete a Company
        • List Company Credit Limits
      • Quotes
        • Retrieve a Quote
        • List All Quotes
        • Update a Quote
        • Create a Quote
        • Accept a Quote
        • Reject a Quote
    • Timelines
      • Retrieve a Timeline
      • List Timelines
      • Create a Timeline
    • S3 Files
      • List all S3Files
      • Create an S3File
      • Delete an S3File
    • Notifications
      • List all Notifications
      • Update a Notification Status
    • User Details
      • List User Details
      • Update User Details
    • Milestones
      • Retrieve a Milestone
      • List Milestones
      • Create a Milestone
      • Delete a Milestone
      • Update a Milestone
  • Webhooks
    • Webhooks
    • Trackings
      • Tracking Webhooks
      • Sea
        • Sea Tracking Event Created
        • Sea Tracking Event Updated
      • Air
        • Air Tracking Event Created
        • Air Tracking Event Updated
    • Customs
      • Customs Declaration Status Changed
      • NCTS Declaration Status Changed
      • ENS Declaration Status Changed
      • GVMS Declaration Status Changed
  1. Webhooks

Webhooks

Modaltrans can push events to your own HTTPS endpoint as they happen, so you do not have to poll the API or wait for a file. When something happens we send one signed POST with a JSON body. You return a 2xx and the delivery is done. Anything else counts as a failure, and failures that look temporary are retried for up to 22 hours.
This page covers what is the same for every event type: how an endpoint is created, how to verify the signature, how to answer, what we retry and when an endpoint is switched off. What each event carries is described on its own page, starting with Tracking Webhooks.

Setting it up#

Two sides are involved and it helps to be clear about who does what.
The receiving side (your IT team, your software supplier, or your customer's) builds the address that accepts the deliveries and decides what that address is. Modaltrans never invents it.
The Modaltrans side (a colleague with access to your Modaltrans settings) enters that address in Modaltrans, ticks the events and hands the signing secret back to the receiving side.
Before the endpoint is created, the receiving address has to:
be reachable from the public internet, and stay that way: no internal hostname, no private IP address, no VPN-only address. If the address later starts resolving to an internal address, deliveries stop and they are not retried;
answer over https with a valid certificate, since plain http is rejected when you save;
accept a POST with a JSON body and answer within 10 seconds, see Responding below.
Then, in Modaltrans:
1.
Go to Settings > Integration Settings > Webhook Endpoints and select New Webhook Endpoint.
2.
In Name, write who it belongs to, for example "Acme Ltd production". It is only a label for your own list.
3.
In Target URL, paste the address the receiving side gave you.
4.
Leave For a specific customer unticked and every event your account tracks is forwarded to that address, which is what you want for a system of your own. Tick it and pick the customer when the address belongs to one of your customers, and only that customer's events are sent.
5.
If the receiving side asked for a fixed header of their own, tick Send an extra authentication header and fill in the name and value they gave you, see An extra header of your own below.
6.
Under Subscribed Events, click each kind of record you need, for example Sea tracking event, and tick the events under it.
7.
Save. The endpoint page now shows the Secret. Send it to the receiving side the way you would send a password. Anyone who has it can forge a delivery.
8.
Select Send Test Event, pick one of the subscribed events, and have the receiving side confirm that the request arrived and the signature matched.
The target URL cannot be changed once the endpoint is saved. If the address changes, create a new endpoint and delete the old one; the new endpoint gets a new secret and an empty delivery list. The secret can be replaced at any time with Rotate Secret, and the old one stops working the moment you do, so agree on a time with the receiving side first.
If you cannot see this screen, your Modaltrans user does not have access to the integration settings. Ask the colleague who administers your Modaltrans account to create the endpoint for you.

Verifying the signature#

Every request carries these headers:
HeaderValue
X-Modaltrans-Signaturesha256= followed by the lowercase hex digest
X-Modaltrans-DeliveryDelivery id, identical on every attempt of the same delivery
X-Modaltrans-EventThe event name, for example sea_tracking_events/create
Content-TypeAlways application/json
User-AgentModaltrans-Webhook/1.0
Your signing secret looks like whsec_ followed by 32 random bytes in base64. Use the whole string, prefix included, as the HMAC key; do not strip the prefix and do not base64-decode it.
Compute HMAC-SHA256 over the entire raw request body and compare the result, prefixed with sha256=, to X-Modaltrans-Signature. Sign the bytes exactly as received, before any JSON parsing or re-serialisation; whitespace matters. Use a constant-time comparison, not ==.
You can check your implementation offline against this vector:
secret    whsec_SxQbP1Q6TTh9jC1zBDuvWJH0RGKiKpYcO0mQ4ap9ZmI=
body      {"message":"You shall not pass!"}
signature sha256=d55e674078099a696aebbcb1c4e0094a938fd7902667c8d06de366bcdfcc2741
The signature is computed at the moment we send, with the secret the endpoint holds right then. A retry or a resend after a rotation therefore carries the same body under a different signature, so verify against the current secret rather than a value you stored earlier.
Reject any request whose signature does not match. When the secret is rotated the old one stops working immediately, so read it from configuration rather than hard-coding it.
The signature carries no timestamp, so a request someone captured stays valid for as long as the secret does. Keep the receiving address on TLS, do not log the body where it can be read back, and de-duplicate on X-Modaltrans-Delivery.

An extra header of your own#

Some receivers sit behind an API gateway that rejects anything without a fixed header, before the request ever reaches the application. If that is your case, the endpoint can carry one extra header that we send on every request.
Your Modaltrans administrator ticks Send an extra authentication header on the endpoint and fills in the name and the value you gave them. The value is yours, not ours: it is whatever your gateway already expects. If you have nothing set up yet, the button inside the value field generates a random 32-character key you can then configure on your side.
A few rules, so the header is always a valid one:
The name cannot be Host, Connection, Content-Type, Content-Length, Transfer-Encoding, User-Agent, Accept-Encoding or Expect, and cannot start with X-Modaltrans-. Those are ours, and overwriting them would break either the delivery or the signature.
The value has to be printable ASCII on a single line.
Spaces at either end are removed before saving, so a value pasted with a stray space still works.
The header goes out on every attempt, including retries, manual resends and test events.
It does not replace the signature. Treat it as a filter at your edge, not as proof the request came from us: anyone who learns the value could post to you with it. Verify X-Modaltrans-Signature as well.
The endpoint page shows the header to your Modaltrans administrator; the request preview of each delivery masks the value.

Responding#

Return a 2xx as soon as you have stored the body. Do not validate business rules, look up records or write to downstream systems before answering. If your processing fails after you have accepted the delivery, that is yours to retry; if you answer with an error we will resend the same body and you may end up processing it twice.
A 2xx means "received", not "understood". Any other status is treated as a failure.
Opening the connection may take at most 5 seconds. After that we wait 10 seconds for your answer, and the response body counts. A 200 whose body trickles in after that is a timeout on our side, and the same delivery is sent again, so answer first, answer completely, and do the work afterwards.
We do not keep your response body when the delivery succeeds. When it fails we store the first 2 KB so your administrator can see the error you returned. We still download whatever you send before truncating it, so keep error bodies short and do not put anything sensitive in them.

Retries#

OutcomeWhat we do
2xxDone
5xx, 408, 429, timeout, connection failure, TLS failureRetried, up to five more times over 22 hours
Other 4xxNo retry, the delivery is marked failed
3xxNo retry, give us the final URL instead
A delivery gets up to six attempts: the first one, then five retries.
AttemptRoughly this long after the first attempt
1Immediately
215 minutes
31 hour
46 hours
513 hours
622 hours
Each wait is counted from the attempt before it and is a floor, not a schedule: a retry can arrive a little later than the table says, never earlier, so do not time anything against it. The first 2xx ends the series. If the sixth attempt also fails, or an attempt fails in a way that is not retried, the delivery is marked failed and stays on the endpoint page, where your Modaltrans administrator can resend it; a resend starts a fresh series of six attempts.
Test events are the exception: they are sent once and never retried, see Testing and troubleshooting below.
Retries and manual resends reuse the stored body byte for byte, so X-Modaltrans-Delivery and eventID are identical on every attempt. The signature is the one exception: it is recomputed at send time, so it differs if the secret was rotated in between.

When an endpoint is disabled#

Modaltrans keeps the time of the first failure in an unbroken run of failures, and every failed attempt counts, retries included. The run ends when a delivery succeeds, or when 24 hours pass without any failure; the next failure after that starts a new run. When an attempt fails while the run is already 24 hours old, the endpoint is disabled and the colleague who created it is notified.
So an endpoint that works even once a day is never disabled this way, and neither is one that fails now and then with quiet days in between. A single delivery that fails all six attempts spans 22 hours, so it does not disable an endpoint on its own either; it takes failures that keep coming for a full day.
While an endpoint is disabled, new events are not queued for it. They are skipped, not stored, and they do not arrive later. Retries that were waiting are not sent either: those deliveries are marked failed and stay on the endpoint page. A manual resend still goes out, once. Someone has to set the endpoint back to active by hand once the problem is fixed. After that, Resend Failed on the endpoint page sends the failed deliveries again in one go, see Testing and troubleshooting below. The events that were skipped while the endpoint was disabled have to be asked for separately.

Identifiers and ordering#

Two identifiers do different jobs:
eventID identifies this message. A correction to something you already have arrives as an update event with a new eventID.
modaltrans.eventKey identifies the record as Modaltrans holds it. It stays the same while that record stays the same, so upsert on it. What can make it change is specific to the event type, and the event page says so.
X-Modaltrans-Delivery tells you whether two requests are the same attempt at the same message. It is stable across retries and manual resends, so use it to avoid double-processing, but do not treat a repeat as something to ignore silently: a resend usually means your administrator wants that event applied.
Deliveries are not ordered, and eventCreatedDateTime is the moment we built the message, not the moment anything happened. A create and the update that follows it can carry the same value, so it settles nothing on its own. Apply messages in the order you receive them and let the last one win for a given eventKey.
data is an array today and always holds one element. If we start grouping several events of one shipment into a single delivery, the shape will not change.

Testing and troubleshooting#

Your Modaltrans administrator can send a test event at any time: Send Test Event on the endpoint page lists the events the endpoint is subscribed to. The test delivery is signed and headed exactly like a real one and carries "test": true in the envelope, so you can drop it before it reaches your records. Its body has one field real payloads never have, modaltrans.sentBy, so do not reject it on a strict schema check. A test event is sent once and never retried, so a test that fails is marked failed straight away; fix the receiving side and select Resend on it.
The same page lists the endpoint's deliveries, newest first and ten to a page, under three tabs:
All: every delivery.
Failed: deliveries that will not be tried again, with their number next to the tab.
Will retry: deliveries waiting for another attempt. Attempts shows how far along they are, for example 2 / 6, and Next attempt shows when the next one is due.
Each delivery shows its status, the HTTP code you returned, how many attempts were made, the exact body we sent and, when it failed, the beginning of the answer you gave. That list is the first place to look when something is missing. If the delivery is there and was accepted, the event left us and the problem is on the receiving side. If it is not there at all, the event was never forwarded, and the event page explains what we filter out. A failed delivery stays on the Failed tab until it is resent, and Resend sends it again with the same body and the same delivery id.
After an outage on the receiving side, Resend Failed on the Failed tab sends the failed deliveries again in one go. It takes the newest 500 at a time, queues them ten per second, leaves out test events and events the endpoint is no longer subscribed to, and is only offered while the endpoint is active. Each resent delivery starts a fresh series of six attempts.
Modified at 2026-11-02 12:51:34
Previous
Update a Milestone
Next
Tracking Webhooks
Built with