# Receive status webhooks

Authenticate incoming journey updates, acknowledge receipt, and handle duplicate states.

Company: Metfone Express
API version: v1
Requested language: en
Content language: en
Source: https://mfexpress-partner-docs.zhst.app/mfexpress/en/webhook-status


```http
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


```json
{
  "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

1. Verify `Authorization` against the configured token. This is the partner-issued webhook token, not `X-Api-Token`.
2. Durably store the payload or enqueue it internally.
3. Return HTTP **200** to acknowledge receipt; business processing need not be complete.
4. 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


```mermaid
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 retries
```


## Complete 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}`.

