POST ${WEBHOOK_URL}Delivery contract
The webhook is the official journey-update source for pickup, transport, delivery success/failure, return requests, and cancellation. It updates the journey only; it does not create, edit, or cancel orders. Each partner configures one webhook. The source says changes are pushed sequentially in update order, but also warns that older statuses can be resent. Handle duplicates and prevent older events from regressing a newer state. Do not infer exactly-once delivery or a total ordering across retries.
Configure with TMS
| Field | Required | Description |
|---|---|---|
| partner_account | Yes | Partner account issued by the carrier |
| token | Yes | Secret token supplied by the partner and sent with each request |
| webhook_url | Yes | Partner HTTPS endpoint receiving updates |
Incoming headers
| Header | Value |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer ${WEBHOOK_TOKEN} |
Payload fields
| Field | Type | Required | Description |
|---|---|---|---|
order_code |
string | Yes | Order code. |
post_code |
string | Yes | Current branch/unit code. |
province_code |
string | Yes | Province code of the current unit. |
province_name |
string | Yes | Province name of the current unit. |
department_address |
string | Yes | Current unit address. |
status_code |
integer | Yes | Numeric journey status from the status table. |
status_name |
string | Yes | Journey status label. |
actor_name |
string | Yes | Actor name; personal data must be protected. |
updated_at |
string | Yes | Current status update time, ISO 8601. |
note |
string / null | See description | Current status name and optional note. |
Successful delivery example: 410
{
"order_code": "EXAMPLE-ORDER-001",
"post_code": "EXAMPLE-POST",
"province_code": "SPE",
"province_name": "Kampong Speu",
"department_address": "Example branch, Kampong Speu",
"status_code": 410,
"status_name": "Delivered successfully",
"actor_name": "Example Operator",
"updated_at": "2026-10-14T14:30:00+07:00",
"note": "Delivered successfully"
}Receipt and processing
- Verify
Authorizationagainst the configured token. This is the partner-issued webhook token, notX-Api-Token. - Durably store the payload or enqueue it internally.
- Return HTTP 200 to acknowledge receipt; business processing need not be complete.
- Process order updates/notifications internally, deduplicate, and own internal retries.
Source warning retained: the guide instructs partners to return 200 even on internal business errors, including an unknown order or database failure, to prevent repeated carrier deliveries. That guidance risks losing an event if nothing was persisted. Portal integration recommendation: acknowledge only after durable receipt; if persistence fails or authentication fails, agree an error policy with TMS rather than falsely acknowledging success. This is safety guidance, not an additional provider guarantee.
The source does not specify retry intervals/counts, timeout duration, event IDs, HMAC signatures, or a deduplication-key algorithm. Do not invent those guarantees. Repeated known states should not repeat side effects; retain updated_at and enough reconciliation context for your business rules.
| Result | Carrier action |
|---|---|
| 200 OK | Marks successful; does not retry |
| Non-200 | Retries according to configured count; values unspecified |
| Timeout | Retries after timeout; duration unspecified |
Webhook diagram
View source
sequenceDiagram
participant T as TMS
participant W as Partner webhook
participant Q as Durable inbox or queue
participant B as Business processing
T->>W: POST update with Bearer token
W->>W: Authenticate and validate payload
W->>Q: Durably accept event
Q-->>W: Stored
W-->>T: HTTP 200
Q->>B: Process with deduplication
Note over T,W: Older states may be delivered again
Note over Q,B: Partner owns business retriesComplete status table
| Code | Group | Status |
|---|---|---|
| N/A | New / draft | New |
| 110 | Pickup in progress | Not assigned to a pickup courier |
| 120 | Pickup in progress | Assigned to a pickup courier |
| 200 | Pickup in progress | Courier traveling to pick up |
| 220 | Pickup in progress | Customer requested another pickup time |
| 230 | Pickup in progress | Customer did not hand over goods |
| 210 | Collected / in transit | Goods collected |
| 300 | Collected / in transit | Order added to a load |
| 301 | Collected / in transit | Order removed from a load |
| 302 | Collected / in transit | Load completed |
| 306 | Collected / in transit | Handover received |
| 309 | Collected / in transit | Postal item received |
| 402 | Collected / in transit | Out for delivery |
| 420 | Collected / in transit | Customer requested another delivery time |
| 430 | Collected / in transit | Delivery unsuccessful |
| 460 | Collected / in transit | Return notification |
| 471 | Collected / in transit | Return refused |
| 470 | Collected / in transit | Return approved |
| 500 | Collected / in transit | Returning to sender |
| 512 | Return delivery | Return out for delivery |
| 530 | Return delivery | Customer requested a later return delivery |
| 540 | Return delivery | Return delivery unsuccessful |
| 410 | Final | Delivered successfully |
| 520 | Final | Returned successfully |
| 201 | Final | Order canceled |
Only codes 110, 120, 200, 220, and 230 are eligible for API cancellation. “N/A” for new/draft is not a numeric code to submit. This is a status inventory, not a claim that every shipment follows every state in this order.
OpenAPI callback
The OpenAPI 3.1 export places this operation at webhooks.orderStatus.post. The URL is registered by the partner, not beneath the TMS base URL. The source describes no automatic webhook-registration endpoint. A 200 response body is unspecified; do not invent {success:true}.