Webhooks
Webhooks let Logistall push shipment status changes to your own endpoint instead of you polling /v1/shipments. This is separate from the /v1/* read API — subscriptions are managed in the Logistall web app, not through a public API call.
Subscribing
Section titled “Subscribing”Webhook subscriptions are created from the Webhooks section of the API Keys page (same page where you create API keys — see Authentication → Creating a key and the User Guide’s FAQ). Adding a subscription asks for:
- URL — where Logistall will
POSTthe event payload. - Event — currently only one event type exists:
shipment.status_changed. - Secret (optional) — used to sign deliveries so you can verify they came from Logistall. Strongly recommended; without one, deliveries are sent unsigned.
Subscriptions can be deleted from the same page, which stops future deliveries to that URL.
The event
Section titled “The event”shipment.status_changed fires whenever a shipment’s status field changes via the app (for example, a dispatcher or driver moving a shipment from pickup_scheduled to in_transit).
Payload
Section titled “Payload”{ "event": "shipment.status_changed", "tenantId": "7f2a1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d", "shipmentId": "3f1c9c2a-6b3e-4b3a-9b2d-1a2b3c4d5e6f", "shipmentNumber": "SHP-00482", "oldStatus": "pickup_scheduled", "newStatus": "in_transit", "at": "2026-07-13T09:15:00.000Z"}Content-Type is application/json.
Verifying the signature
Section titled “Verifying the signature”If your subscription has a secret configured, every delivery includes:
X-Logistall-Signature: sha256=<hex-encoded HMAC-SHA256>The signature is an HMAC-SHA256 of the exact raw request body (the JSON text as sent, not a re-serialized version of it), keyed with your webhook’s secret. Compute the same HMAC on your end and compare.
// Node.js (Express) example. Requires the RAW request body as a string/Buffer —// re-stringifying a parsed JSON object can reorder keys and break the comparison.const crypto = require('crypto');
function verifyLogistallSignature(rawBody, signatureHeader, secret) { if (!signatureHeader || !signatureHeader.startsWith('sha256=')) return false;
const expected = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex');
const provided = signatureHeader.slice('sha256='.length);
const a = Buffer.from(expected, 'hex'); const b = Buffer.from(provided, 'hex'); if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);}
// Express route — use express.raw() (not express.json()) so req.body is a Buffer.app.post( '/webhooks/logistall', express.raw({ type: 'application/json' }), (req, res) => { const ok = verifyLogistallSignature( req.body, req.header('X-Logistall-Signature'), process.env.LOGISTALL_WEBHOOK_SECRET ); if (!ok) return res.status(401).send('Invalid signature');
const event = JSON.parse(req.body.toString('utf8')); // ... handle event ... res.status(200).end(); });If your subscription has no secret set, deliveries are sent without the X-Logistall-Signature header — there’s nothing to verify.
Retries
Section titled “Retries”Each delivery attempt has a 5-second timeout. If it fails (network error, timeout, or a non-2xx response), Logistall retries up to 3 attempts total for that delivery, with a short delay before the 2nd and 3rd attempts (roughly 200ms, then 800ms). If all 3 attempts fail, the delivery is marked unsuccessful and is not retried further — there’s no scheduled redelivery after that window.
Your endpoint should respond quickly with a 2xx status once it has durably accepted the event; do heavier processing asynchronously rather than inside the request handler, so you don’t trip the 5-second timeout.
Delivery records
Section titled “Delivery records”Every delivery attempt (URL, status code received, attempt count, success/failure, and the last error if any) is recorded internally against the subscription. As of this writing there is no page in the app or API endpoint to view that history yourself — if a delivery seems to be failing, check your own endpoint’s logs, or reach out with the subscription URL and approximate time so it can be checked server-side.