Build Write-Back Endpoints for TrackFlo
Updated today
Build five HTTPS endpoints in your TMS and we write your team's tracking work in TrackFlo back to the right load: check calls, stop arrivals and departures, status changes, driver and equipment details, and documents. This article covers what all five share: authentication, the request format, how to respond, retries and testing. Each endpoint's article lists every field it carries.
Use this when your TMS isn't one we connect to out of the box. For McLeod, Turvo, Mastery, Tai and Revenova, we write through the TMS's own API and you build nothing; see How TrackFlo Connects to Your TMS.
The five endpoints
Endpoint | Suggested path | We send it when |
|---|---|---|
| A tracker saves a check call, an update read from a driver or carrier message is approved, or TrackFlo records an action on the load, such as a driver's text reply | |
| A stop's arrival or departure is recorded | |
| A tracker changes the load's status, or a stop event moves it to a new one | |
| The driver, truck, trailer or carrier tracking link is added or changed | |
| A POD is uploaded in TrackFlo, or collected from the carrier and approved |
The paths are a suggestion; any HTTPS URL works. Send us the URL of each endpoint you build. You can build them in any order, and an update for an endpoint you haven't built stays in TrackFlo only.
Before you start
HTTPS only, TLS 1.2 or later. We connect from a fixed IP address, which we give you during onboarding so your firewall can allow it.
An API key you issue to TrackFlo. We store it encrypted in AWS Secrets Manager.
The ids you already send us. Every request names the load, leg and stop by the ids your TMS sent to the Integration API, so you look records up by your own keys.
A staging environment, if you have one. We test every endpoint there before your team relies on it.
Authentication
Every request carries two headers: your key, so you know the caller is allowed, and our signature, so you know the body came from us unchanged.
Authorization: Bearer <the key you issued to TrackFlo>
X-TrackFlo-Signature: t=1792675200,v1=5f2b1c...Authorization: reject a missing or wrong key with401.X-TrackFlo-Signature:tis the Unix time we signed at.v1is the hex HMAC-SHA256 of<t>.<raw request body>, keyed with the signing secret we give you at onboarding. Recompute it over the raw bytes before parsing the JSON, compare in constant time, and reject the request with401if it doesn't match or iftis more than 5 minutes from your clock.
import hashlib, hmac, time
def verify_trackflo_signature(raw_body: bytes, header: str, secret: str) -> bool:
pairs = [part.split("=", 1) for part in header.split(",")]
timestamp = next(value for key, value in pairs if key == "t")
if abs(time.time() - int(timestamp)) > 300:
return False
signed = timestamp.encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, value) for key, value in pairs if key == "v1")When a key or secret is rotated, we agree a switch date with you. Accept both the old and new key until we confirm the switch. While a signing secret rotates, the header carries two v1 values; accept the request if either matches.
Request format
Every request is a POST with a UTF-8 JSON body and these headers:
Content-Type: application/json
Idempotency-Key: 6f1d2c0e-8a43-4f0b-9a57-2b8d3e1c4f90The body is the shared fields below plus the endpoint's own fields.
Field | Type | Description |
|---|---|---|
| string (UUID) | Unique to this update, and the same on every retry of it. Matches the |
| string |
|
| string | Your order or shipment id, as you sent it to TrackFlo. |
| string | Your movement, route or trip id, as you sent it to TrackFlo. Write to this leg: on a split order, each leg is a different carrier. |
| string | The person who made the update, or |
| string or null | The person's email. Null for automated updates. |
| string or null | The person's user id in your TMS, when you've given us that mapping. Null otherwise. |
| string | When the update was saved in TrackFlo. |
| string | A link to the load's tracking page in TrackFlo, so your team can open it from the record. |
{
"event_id": "6f1d2c0e-8a43-4f0b-9a57-2b8d3e1c4f90",
"event_type": "load_update",
"native_system_load_id": "1089520",
"native_system_load_leg_id": "2330158",
"entered_by_name": "Jordan Ellis",
"entered_by_email": "[email protected]",
"entered_by_tms_user_id": "JELLIS",
"entered_at_utc": "2026-10-24T15:42:10Z",
"trackflo_load_url": "https://app.trackflo.tech/public_load_details/4b6e…"
}Timestamps
We send the same timestamp pairs the Integration API uses, always both halves when we know the location's timezone.
_utc: ISO-8601 in UTC with aZ, for example2026-10-24T15:37:00Z._local: the wall-clock time at the stop or location, no offset, for example2026-10-24T10:37:00. Its timezone is in the endpoint's*_iana_timezonefield.
Responding
Answer within 30 seconds. We treat no answer as a failure and retry.
Your response | What we do |
|---|---|
| Success. We store your id with the update. |
| Success. Return the original record's id if you can. |
| No retry. The tracker sees your |
| No retry. Our team is alerted to fix the credentials. |
| The load, leg or stop isn't in your TMS. No retry; the tracker sees your |
| Retry after |
| Retry. |
Send errors in this shape:
{ "error": "stop_not_found", "message": "Stop 88213 is not on movement 2330158." }Answer 2xx only once the update is saved, or queued somewhere it can't be lost. We read every 2xx as success, including one with an error in its body, and the tracker is never told the update didn't land.
Retries and duplicates
We retry a failed request up to 5 times, waiting longer each time, over about 25 minutes. Every retry carries the same event_id. If the last attempt fails, the person who made the update gets an email and a notification in TrackFlo, and the update stays saved in TrackFlo.
Store
event_idand refuse a repeat. A request that timed out may still have been saved on your side, so its retry is a duplicate. Answer the repeat with409, or with the same2xxas the first time.Don't rely on arrival order. Two updates on the same load can arrive out of order, especially when one was retried. Order by each endpoint's event time (
load_update_utc_timestamp,arrived_date_time_utc,status_date_time_utc,entered_at_utc).
Don't send our updates back to us
If your TMS also sends load updates to TrackFlo through the Integration API, skip the records these endpoints created. Otherwise each update shows twice on the load's timeline. Comments we write end in (via TrackFlo), so you can also filter on that.
Testing and go-live
Build the endpoints in staging and send us their URLs and a staging key. We send you the signing secret.
We send each event type against a test load in your staging TMS and check with you that it landed where your team expects.
Send us the production URLs and key.
Your team's next updates in TrackFlo are written to your TMS.