Guide to FourKites Webhooks: Features and Best Practices
FourKites webhooks, which FourKites calls callbacks, push shipment visibility data to your systems as it happens: a load is created, a truck's location updates, an ETA changes, the load arrives at or leaves a stop, a stop is completed, or proof of delivery is uploaded. If you track freight with FourKites, the supply-chain visibility platform, callbacks are how that data reaches your TMS, ERP or customer portal without polling.
This guide covers how FourKites callback subscriptions work, the main callback messages and their payloads, how to protect your endpoint with Basic credentials, FourKites's retry rules, and the best practices for production.
What are FourKites webhooks?
FourKites sells its callbacks as a licensed product called Standard Webhooks. The name is FourKites's own, and the product has nothing to do with the open Standard Webhooks specification.
Each callback is an application event. When a subscribed event occurs, FourKites sends a real-time JSON payload in an HTTP POST to your endpoint. Subscriptions aren't self-serve: you supply FourKites with your URL, the callback messages you want, an authentication method, and any additional headers, and FourKites stores the configuration.
FourKites doesn't sign callbacks. It supports three authentication methods:
- Basic: HTTP Basic credentials.
- API Key / Auth Header: keys or secrets inside HTTP headers.
- OAuth2 with the client credentials flow: FourKites fetches a token from your token URL using a
client_idandclient_secret, and expects the token in anaccess_tokenJSON key.
FourKites webhook features
| Feature | Details |
|---|---|
| Product | FourKites Standard Webhooks, a licensed solution |
| Subscription | Arranged with FourKites: URL, callback messages, authentication method and additional headers |
| Method and format | POST with a JSON body |
| Event identifier | MessageType in the body |
| Authentication | Basic, API Key / Auth Header, or OAuth2 client credentials. No signature |
| Retries | 5xx only, after 1 minute, 15 minutes, 1 hour and 24 hours |
4xx responses | Never retried |
| Monitoring | FourKites doesn't monitor whether your endpoint succeeds |
| Source IPs | Static ranges for staging and production, available from your FourKites Implementation or Customer Success Manager |
Common events
FourKites's Callbacks Data Dictionary Table lists its recommended callbacks:
| Callback | MessageType |
|---|---|
| Load Creation | LOAD_CREATION |
| Location Update | LOCATION_UPDATE |
| Tracking Update | TRACKING_UPDATE |
| Stop ETA Updated | STOP_ETA_UPDATED |
| Stop Arrival | STOP_ARRIVAL |
| Stop Departure | STOP_DEPARTURE |
| Stop Completion | STOP_AUTO_COMPLETED, STOP_AUTO_DELIVERED, STOP_AUTO_PICKED_UP, MANUALLY_COMPLETED, MANUALLY_DELIVERED, MANUALLY_PICKED_UP |
| Stop File Upload | STOP_FILE_UPLOAD |
Note that Stop Completion has six MessageType values, depending on whether the stop was completed automatically or manually and whether it was a pickup, a delivery or neither. Route on all six. Other callback pages, including the Network Visibility and appointment callbacks, document more message types, so treat this list as a starting point.
Each callback is a flat JSON object. Here is FourKites's LOAD_CREATION example, trimmed:
{
"MessageType": "LOAD_CREATION",
"IsSuccess": true,
"Errors": [],
"FourKitesLoadId": 123654314,
"LoadNumber": "2DB85AEB",
"ProNumber": "4638",
"Scac": "CC79",
"Shipper": "cytc-company-new",
"ReferenceNumbers": ["TU-2945", "80982", "94BC8"],
"Tags": ["temp", "field2", "Inbound"],
"EncryptedUrl": "https://app.fourkites.com/beta/eurl/...",
"Timestamp": "2021-06-03T17:08:33+00:00",
"Timezone": "Etc/UTC",
"TimezoneOffset": 0,
"TimezoneShortName": "UTC"
}
A few fields need care:
IsSuccessandErrorstell you whether load creation actually worked. When it failed, there's noFourKitesLoadId.LoadNumberis your own identifier, supplied when you created the load.FourKitesLoadIdis FourKites's.- Stop timestamps are in the stop's local time with a UTC offset, and
TimezoneOffsetis in seconds. - On
STOP_FILE_UPLOAD,FileUrlis only valid for 24 hours, and FourKites advises downloading the file as soon as the message arrives. EncryptedUrlis a shared shipment status page that needs no authentication. Treat it as sensitive.
Setting up FourKites webhooks
- Choose a username and a long random password, for example with
openssl rand -hex 32. - Ask your FourKites contact to subscribe your HTTPS endpoint to the callback messages you want, with Basic as the authentication method and your username and password.
- Ask for the static IP ranges for staging and production if you want to restrict traffic by IP.
In Hookdeck. Create a source with the FourKites type and enter the same username and password. Give FourKites the source URL as the callback URL. Hookdeck reads the Authorization: Basic header and compares both values.
FourKites's docs don't say whether the Basic credentials are sent with the first request or only after a server answers 401 with a challenge. Hookdeck never sends a challenge, so FourKites has to send the credentials preemptively, on the first request. Confirm this with FourKites when you subscribe and check the first callbacks in Hookdeck. If they show as failed verification, ask FourKites to send the header through the subscription's Additional Headers instead, as Authorization with the value Basic followed by the base64 of username:password.
The FourKites source type only checks Basic credentials. If your subscription uses API Key / Auth Header, use a Hookdeck source with API key verification instead. OAuth2 has FourKites fetch a token from a server you run, which a fixed check can't verify.
Securing FourKites webhooks
Decode the Authorization header, split it on the first colon, and compare the username and password with your stored values in constant time.
const crypto = require('crypto');
const express = require('express');
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on buffers of different lengths
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
function verifyBasicAuth(header, user, pass) {
if (!user || !pass) return false; // fail closed when unconfigured
const match = /^basic\s+(\S+)\s*$/i.exec(header || '');
if (!match) return false;
const decoded = Buffer.from(match[1], 'base64').toString('utf8');
const i = decoded.indexOf(':'); // the password may contain ':'
if (i === -1) return false;
const userOk = safeEqual(decoded.slice(0, i), user);
const passOk = safeEqual(decoded.slice(i + 1), pass);
return userOk && passOk;
}
const app = express();
app.post('/webhooks/fourkites', express.raw({ type: '*/*' }), (req, res) => {
const ok = verifyBasicAuth(
req.get('authorization'),
process.env.FOURKITES_WEBHOOK_USERNAME,
process.env.FOURKITES_WEBHOOK_PASSWORD
);
// Returning 401 here means FourKites won't retry: 4xx responses are final.
if (!ok) return res.sendStatus(401);
let message;
try {
message = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Route on message.MessageType, deduplicate, process async.
res.sendStatus(200);
});
The same check in Python:
import base64
import binascii
import hmac
import re
def verify_basic_auth(header, user, password) -> bool:
if not user or not password:
return False # fail closed
match = re.fullmatch(r"(?i)basic\s+(\S+)\s*", header or "")
if not match:
return False
try:
decoded = base64.b64decode(match.group(1), validate=True)
except (binascii.Error, ValueError):
return False
received_user, sep, received_pass = decoded.partition(b":")
if not sep:
return False
user_ok = hmac.compare_digest(received_user, user.encode("utf-8"))
pass_ok = hmac.compare_digest(received_pass, password.encode("utf-8"))
return user_ok and pass_ok
Basic credentials prove the sender knows them. They don't prove the body is unchanged, and a captured request can be replayed and still pass, because FourKites sends no signature and no timestamp. Use HTTPS only, keep the password long and random, and restrict traffic to FourKites's IP ranges as a second layer.
Make FourKites callbacks production-ready. Hookdeck Event Gateway checks the Basic credentials on every callback, accepts each one straight away, and queues it durably with retries and replay.
FourKites webhook limitations and pain points
A 4xx is final
The Problem: A bug that makes your handler return 400 or 422, or a credentials mistake that returns 401, loses every callback it touches.
Why It Happens: FourKites retries only 5xx responses. "For any 4XX code responses, retries will not be applied."
Workarounds:
- Return
5xxfor problems on your side that a retry could fix, such as a database outage, and keep4xxfor requests that are genuinely bad. - Don't let parsing or validation bugs surface as
4xxbefore the event is stored. Store first, then validate.
How Hookdeck Can Help: Hookdeck accepts each callback as soon as it arrives, so FourKites always sees success, and then delivers it to your service with its own retries. A bug in your handler becomes a failed delivery you can replay after the fix, not a lost callback.
Four retries, and nobody watching
The Problem: An outage longer than about a day loses data, and nothing tells you.
Why It Happens: FourKites retries 5xx responses after 1 minute, 15 minutes, 1 hour and 24 hours, then stops. It also says it "does not actively monitor success/failure of HTTP responses from customer endpoints", and recommends that you log and monitor what you receive.
Workarounds:
- Monitor callback volume per
MessageTypeand alert when it drops. - After an incident, reconcile active loads through the FourKites API.
How Hookdeck Can Help: Hookdeck logs every callback it receives and every delivery to your service, and issues alert you when deliveries fail, so the monitoring FourKites asks for comes built in.
Basic credentials, with an unknown handshake
The Problem: Callbacks fail verification even though the username and password are right.
Why It Happens: FourKites doesn't document whether it sends Basic credentials on the first request. A client that waits for a 401 challenge never authenticates against an endpoint that doesn't send one, and Hookdeck doesn't.
Workarounds:
- Confirm with FourKites that credentials are sent preemptively.
- If they aren't, have FourKites send the
Authorizationheader through Additional Headers.
How Hookdeck Can Help: Hookdeck's FourKites source type checks the Basic username and password on every request. Hookdeck answers FourKites with a 200 either way, then records a request with missing or wrong credentials as failed verification, so it never reaches your destination, and you can see straight away whether credentials are arriving.
Proof-of-delivery links expire
The Problem: A POD file you try to fetch later returns an error.
Why It Happens: The FileUrl in STOP_FILE_UPLOAD is only active for 24 hours.
Workarounds:
- Download the file in a background job as soon as the callback arrives, and store it yourself.
- Alert on download failures, because the window to retry is short.
How Hookdeck Can Help: Because Hookdeck delivers each callback to your service promptly and retries failures quickly, your download job gets the URL while it's still valid, even if your service was briefly down when FourKites sent it.
Best practices
Route on MessageType, including all six completion types
Switch on MessageType, and make sure Stop Completion handling covers both the automatic and manual variants. Acknowledge types you don't recognise.
Deduplicate deliveries
FourKites payloads have no delivery ID. Build a key from MessageType, FourKitesLoadId (or LoadNumber), the stop identifier where there is one, and Timestamp. See our guide to webhook idempotency.
Expect every key, with empty values
FourKites says every key is always present, with null or [] when an optional value is empty. Check values, not the presence of keys.
Acknowledge fast and process asynchronously
Store the callback, return 200, and do the work, such as POD downloads and TMS updates, in a background job. See why to process webhooks asynchronously.
Mind time zones
Stop times are local to the stop. Convert with the offset before comparing events from different stops.
Conclusion
FourKites sends a JSON POST for each callback message you subscribe to, protected by Basic credentials rather than a signature. Check those credentials in constant time over HTTPS, make sure FourKites sends them on the first request, route on MessageType, download POD files within 24 hours, and never answer a recoverable problem with a 4xx, because FourKites won't retry it.
Hookdeck Event Gateway sits in front of your receiver, checks the Basic credentials, accepts every callback immediately, and keeps it until your service has processed it.
Get started with Hookdeck for free and handle FourKites callbacks reliably in minutes.