V1 · Guides
Webhooks
A webhook is a message the platform sends to your server when something happens to one of your orders or your account, so you do not have to keep asking.
Setting it up
Webhooks are set on the Application, in the merchant portal: the address to send to (HTTPS only) and the events you want. There is no API endpoint that changes them. That is deliberate, so an integration cannot quietly redirect its own events.
When you add a webhook address, the portal shows a signing secret once. Keep it: you need it to check each message.
What you receive
Each message is an HTTPS POST with a JSON body: the event name, the time, and the data about what happened.
POST /your/webhook/address HTTP/1.1
Content-Type: application/json
X-Swiftazu-Event: order.status_changed
X-Swiftazu-Signature: v1=5d6c0f2a8a1b4c7e9f0a3b2c1d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6
X-Swiftazu-Delivery-Attempt: 1
{
"event": "order.status_changed",
"occurredAt": "2026-10-02T02:30:52.216Z",
"data": {
"id": "dffcd25d-6eb3-4b90-868c-6deca160ad74",
"orderId": "SWZORDYFSADMR",
"awbNumber": "FLC0000001042",
"merchantReference": "WEB-10482",
"status": { "code": "picked_up", "name": "Picked Up" },
"previousStatus": "pickup_assigned",
"reason": null
}
}Headers
| Field | Type | Description |
|---|---|---|
X-Swiftazu-Eventrequired | string | The event name. |
X-Swiftazu-Signaturerequired | string | The signature of the body (see below). |
X-Swiftazu-Delivery-Attemptrequired | number | 1 for the first try, then 2, 3, 4 on retries. |
Checking the signature
Every message is signed so you can be sure it came from the platform and was not changed on the way. The signature is an HMAC-SHA256 of the exact body bytes, made with your signing secret, written as v1= followed by the hex digest.
Compute the same value over the raw body you received (before parsing the JSON) and compare. If it differs, ignore the message.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the exact bytes received, BEFORE JSON.parse.
export function isFromSwiftazu(rawBody, signatureHeader, signingSecret) {
const expected = "v1=" + createHmac("sha256", signingSecret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}Answering
Answer with any 2xx status within 10 seconds. Do the real work after answering. The same event can arrive more than once, so make your handler safe to run twice.
Retries
A failed delivery is retried three times: after 1 minute, 10 minutes and 60 minutes. Every try is kept in the delivery log on the Application, with what was sent and what your server answered. You can replay any delivery from there.
If every delivery to your address fails for 24 hours in a row, the address is switched off. You get a warning by email at 20 hours. Switch it back on from the Application once your server is fixed.
Demo mode
Demo orders send the same webhooks as live orders, so you can test your handler fully before going live.
Events
| Event | Sent when |
|---|---|
order.created | An order is created. |
order.dispatched | An order is dispatched and its air waybill is issued. |
order.status_changed | Any status or sub-status change is accepted. |
order.received_at_hub | A hub scans the shipment in. |
manifest.closed | A hub closes an outbound manifest that holds your shipments. |
manifest.received | A hub receives a manifest that holds your shipments, with any discrepancy. |
order.picked_up | Collection from your pickup address is confirmed. |
order.customs_event | A customs status or document event happens. |
order.out_for_delivery | A delivery attempt begins. |
order.delivered | Delivery is completed with proof. |
order.delivery_failed | A delivery attempt fails. |
order.returned | A return is confirmed received. |
order.cancelled | An order is cancelled. |
cod.collected | Cash is collected at delivery. |
settlement.issued | A settlement batch is confirmed. |
statement.issued | A statement is generated for a postpaid account. |
wallet.low_balance | Your wallet falls below its low-balance level. |
invoice.issued | An invoice is generated for your account. |
payment.failed | A payment attempt fails. |
usage.threshold | The shipping company's plan passes its monthly order limit. |