Webhook 整合指南
Webhook 是什麼?
Webhook 就是 Superroute 在事件發生時(訂單建立、配送完成、追蹤更新等),向您設定的 URL 發出的一次 HTTP POST。您建立一個接收端點,我們把事件推送過去。
投遞機制
事件入佇列後非同步發送。每次請求都帶 HMAC-SHA256 簽章,您可驗證它確實來自我們。投遞失敗(非 2xx 或逾時)會以指數退避自動重試最多 5 次。
安全模型
您在設定頁配置共用密鑰,每筆出站 webhook 皆以此簽章。接收端用相同密鑰重新計算並比對 — 一致代表內容真實未經竄改。
事件目錄
共八種出站事件類型。每種事件在設定頁有獨立 URL 欄位,可依需求訂閱任意子集。
本地配送訂單(送貨 D / 取件 P / 點對點 P2P)建立時觸發,涵蓋所有入口:網頁表單、REST/GraphQL API、電商平台同步、自動規則、匯入列等。Label-service 及其他非配送類型訂單不觸發。若同一收件方同時設定了 order_create_async_postback_url,批次匯入流程中將略過此 webhook(由批次回呼統一通知)。對應欄位 order_create_webhook_url。
載荷範例
{
"result": true,
"id": 1001,
"ref": "REF-001",
"type": "D",
"customer_id": 42,
"tracking_number": [
"SR000000001"
],
"shipping_price": "13.30",
"price_details": {
"shipping_fee": "10.00",
"signature_fee": "1.50",
"sub_total": "11.50",
"tax_details": [
{
"tax_number_id": 3,
"tax_name": "HST",
"tax_rate": 13,
"tax": "1.50"
}
],
"tax_zone_id": 7
},
"currency": "CAD",
"packages": [
{
"id": 2001,
"ref": "PKG-001",
"tracking_number": "SR000000001",
"external_tracking_number": "1Z999AA10123456784"
}
]
}
訂單狀態每次變更時觸發 — 取件、運送中、已送達、異常、取消等。對應欄位 order_status_change_webhook_url。
載荷範例
{
"id": 50001,
"user_id": 3011,
"business_id": 42,
"location_id": 12,
"location_information": {
"name": "Toronto Depot",
"address_1": "10 King St",
"address_2": "",
"city": "Toronto",
"province": "ON",
"country": "Canada",
"postcode": "M5V1A1",
"territory_id": 7,
"territory_name": "Downtown"
},
"grid_id": null,
"ip_address": "203.0.113.24",
"operation_category": 1,
"operation_type": 1001,
"is_ascan": 0,
"operation_description": null,
"route_id": 8801,
"order_id": 1001,
"before_status": 20,
"after_status": 8,
"tracking_event_id": 90001,
"gps_tracking_id": 77012,
"latitude": "43.6532",
"longitude": "-79.3832",
"order_address_history_id": null,
"created_at": "2026-09-16 11:18:57",
"updated_at": "2026-09-16 11:18:57",
"action": "created",
"operation_type_text": "driver change status",
"operation_category_text": "driver",
"order_ref": "REF-001",
"package_id": 2001,
"tracking_number": "SR000000001",
"external_tracking_number": "1Z999AA10123456784",
"type": "D",
"return_reason": "",
"shipping_to": {
"name": "Jane Doe",
"company_name": "",
"telephone": "+14165550123",
"email": "jane@example.com",
"address_1": "55 Queen St W",
"address_2": "Unit 8",
"city": "Toronto",
"province": "ON",
"country": "Canada",
"code": "",
"postcode": "M5H2M9"
},
"shipping_from": {
"name": "Toronto Depot",
"company_name": "Acme Logistics",
"telephone": "+14165550100",
"address_1": "10 King St",
"address_2": "",
"city": "Toronto",
"province": "ON",
"country": "Canada",
"code": "",
"postcode": "M5V1A1"
}
}
包裹生命週期事件觸發時(提交資訊、開始配送、配送成功、未送達等)。對應欄位 tracking_event_webhook_url。「已送達」與「已取件」事件還會附帶簽收憑證:proof_files 以及 proof_files_detail(file_id、type、url、full_url、帶簽章的下載網址)。事件之後才上傳的照片會透過 pod.files_updated 推送。每個檔案還附帶對應事件資訊:tracking_event_id、tracking_event_status_id、tracking_event_key、service_type(1 = delivery / 2 = pickup)和 service_status(1 = success / 2 = failed);未記錄事件的歷史檔案這些欄位為 null。
載荷範例
{
"id": 90001,
"order_id": 1001,
"order_ref": "REF-001",
"location_id": 12,
"location_name": "Toronto Depot",
"tracking_event_type": "D",
"tracking_event_status_id": 500,
"tracking_event_key": "deliver_success",
"otep_status": "delivered",
"proof_files": [
"storage/2026-07-20/1001_S_Ab3kQ9xYzLm4Np.jpeg",
"storage/2026-07-20/1001_P_Cd7rT2wVuKe8Hs.jpeg"
],
"proof_files_detail": [
{
"file_id": 234567,
"type": 1,
"url": "storage/2026-07-20/1001_S_Ab3kQ9xYzLm4Np.jpeg",
"full_url": "https://api.superroute.ca/storage/2026-07-20/1001_S_Ab3kQ9xYzLm4Np.jpeg",
"signed_url": "https://api.superroute.ca/files/pod/234567?expires=1784748600&signature=8f2c1d...",
"signed_url_expires_at": 1784748600,
"tracking_event_id": 90001,
"tracking_event_status_id": 500,
"tracking_event_key": "deliver_success",
"service_type": 1,
"service_status": 1
},
{
"file_id": 234568,
"type": 2,
"url": "storage/2026-07-20/1001_P_Cd7rT2wVuKe8Hs.jpeg",
"full_url": "https://api.superroute.ca/storage/2026-07-20/1001_P_Cd7rT2wVuKe8Hs.jpeg",
"signed_url": "https://api.superroute.ca/files/pod/234568?expires=1784748600&signature=41ba90...",
"signed_url_expires_at": 1784748600,
"tracking_event_id": 90001,
"tracking_event_status_id": 500,
"tracking_event_key": "deliver_success",
"service_type": 1,
"service_status": 1
}
],
"description": {
"en": "Your parcel has been delivered successfully. Thank you!",
"fr": "Votre colis a été livré avec succès. Merci !",
"chs": "您的包裹已成功送达,感谢您的使用!",
"cht": "您的包裹已成功送達,感謝您的使用!",
"es": "Tu paquete ha sido entregado con éxito. ¡Gracias!",
"de": "Ihr Paket wurde erfolgreich zugestellt. Vielen Dank!",
"nl": "Uw pakket is succesvol afgeleverd, bedankt voor uw gebruik!",
"pt": "Seu pacote foi entregue com sucesso. Obrigado!",
"it": "Il tuo pacco è stato consegnato con successo. Grazie!",
"sr": "Vaš paket je uspešno dostavljen, hvala vam što koristite naše usluge!",
"hu": "Csomagja sikeresen kiszállításra került, köszönjük hogy használta szolgáltatásunkat!",
"pl": "Twoja paczka została dostarczona pomyślnie. Dziękujemy!",
"sk": "Váš balík bol úspešne doručený. Ďakujeme!",
"cs": "Váš balík byl úspěšně doručen. Děkujeme!"
},
"tracking_number": [
"SR000000001"
],
"external_tracking_number": [
"1Z999AA10123456784"
]
}
批次訂單匯入處理完畢後觸發一次,載荷含每列處理結果。對應欄位 order_create_async_postback_url。
載荷範例
[
{
"result": true,
"id": 1001,
"ref": "REF-001",
"type": "D",
"customer_id": 42,
"tracking_number": [
"SR000000001"
],
"shipping_price": "13.30",
"currency": "CAD",
"packages": [
{
"id": 2001,
"ref": "PKG-001",
"tracking_number": "SR000000001",
"external_tracking_number": "1Z999AA10123456784"
}
]
},
{
"result": false,
"message": "Please provider a valid postcode",
"ref": "REF-002"
}
]
簽收照片或簽名被新增、取代或刪除時觸發(action:added / updated / removed)——每個檔案一次推送,無需再輪詢附件。設定 pod_files_webhook_url 後啟用。每個檔案還附帶對應事件資訊:tracking_event_id、tracking_event_status_id、tracking_event_key、service_type(1 = delivery / 2 = pickup)和 service_status(1 = success / 2 = failed);未記錄事件的歷史檔案這些欄位為 null。
載荷範例
{
"action": "added",
"order_id": 1001,
"order_ref": "REF-001",
"tracking_number": [
"SR000000001"
],
"external_tracking_number": [
"1Z999AA10123456784"
],
"file": {
"file_id": 234567,
"type": 1,
"note": null,
"url": "storage/2026-07-20/1001_S_Ab3kQ9xYzLm4Np.jpeg",
"full_url": "https://api.superroute.ca/storage/2026-07-20/1001_S_Ab3kQ9xYzLm4Np.jpeg",
"signed_url": "https://api.superroute.ca/files/pod/234567?expires=1784748600&signature=8f2c1d...",
"signed_url_expires_at": 1784748600,
"tracking_event_id": 90001,
"tracking_event_status_id": 500,
"tracking_event_key": "deliver_success",
"service_type": 1,
"service_status": 1
},
"event_id": "9b2e4c7a-1f30-4d8e-8a55-6c0f1e2d3a4b",
"event_time": "2026-09-16T11:18:57-04:00",
"event_timestamp": 1789571937
}
訂單被永久刪除時觸發,便於你的系統同步移除。設定 order_deleted_webhook_url 後啟用。
載荷範例
{
"action": "deleted",
"order_id": 1001,
"order_ref": "REF-001",
"type": "D",
"orders_status_id": 2,
"tracking_number": [
"SR000000001"
],
"external_tracking_number": [
"1Z999AA10123456784"
],
"event_id": "4c1a7f92-0b6d-4e11-9c3a-2f7b5d8e6a10",
"event_time": "2026-09-16T11:18:57-04:00",
"event_timestamp": 1789571937
}
取消請求被拒絕時觸發(例如訂單已在派送中),便於你的營運管道監控失敗的取消,無需輪詢 API 回應。設定 order_cancel_failed_webhook_url 後啟用。
載荷範例
{
"result": false,
"code": "ORDER_ALREADY_IN_DELIVERY",
"message": "Order can no longer be cancelled at its current status",
"http_status": 409,
"submitted": {
"order_id": 1001,
"tracking_number": "SR000000001",
"external_tracking_number": null
}
}
路線看板席位易主或看板狀態變化時觸發——action 欄位說明發生了什麼(claimed、standby、pooled、promoted、withdrawn、vetoed、replaced、assigned、awarded、lost、displaced、settled、board_opened、board_closed、board_cancelled)。僅限企業層級設定。透過 route_board_webhook_url 訂閱。
載荷範例
{
"event": "route_board.seat_changed",
"action": "claimed",
"board": {
"id": 12,
"title": "Thursday AM routes",
"date": "2026-09-10",
"mode": "route",
"status": "open",
"business_id": 7,
"broker_id": null
},
"seat": {
"id": 301,
"route_id": 940,
"slot_id": 950,
"route_name": "North Loop",
"work_from": "08:00",
"work_end": "12:00",
"status": "filled"
},
"claim": {
"id": 5001,
"driver_id": 920,
"driver_name": "Alex Driver",
"driver_alias": "AD",
"rank": 1,
"status": "primary",
"bid_price": null,
"awarded_price": null,
"reason": null
},
"occurred_at": "2026-09-16T11:18:57-04:00",
"extra": {}
}
供應商智慧櫃事件
面向對接智慧櫃的第三方配送供應商的獨立 webhook 通道。事件投遞到你供應商帳號設定的端點,每個端點可訂閱任意事件子集。
已開門 — 投櫃嘗試的櫃門打開瞬間觸發——無論司機是在櫃機螢幕上輸入送件碼開門,還是透過遠端開門 API 開門——換格重開也會觸發。opening 資料塊列出每個已打開格口的 grid_id、硬體門板號 compartment_number 和自提櫃格口號 pickup_locker_number(按列從上到下、再從左到右的顯示序號)。每個格口還帶 pickup_locker_code,即收件人要去的「{貨架編碼}-{格口號}」標籤;格口沒有格口號時為 null。負載中還包含 pickup_code——收件人取件碼,開門瞬間即已分配;司機確認放入後仍是同一個碼,但只有在確認放入之後才能用於取件。
載荷範例
{
"event_id": "PLE-01JABCDEE",
"event_type": "partner_locker.delivery.doors_opened",
"livemode": true,
"occurred_at": "2026-07-21T14:29:40-04:00",
"data": {
"delivery_no": "PLD-100234",
"external_order_id": "EXT-9001",
"status": "awaiting_confirmation",
"delivery_mode": "standard",
"pickup_code": "483920",
"opening": {
"attempt_id": 501,
"attempt_no": 1,
"opened_count": 2,
"failed_count": 0,
"partial": false,
"groups": [
{
"shelf_name": "LK-A1",
"locker_identifier": "SL-88231",
"compartments": [
{ "package_sequence": 1, "compartment_number": "12", "grid_id": 5011, "pickup_locker_number": 3, "pickup_locker_code": "LK-A1-3" },
{ "package_sequence": 2, "compartment_number": "15", "grid_id": 5014, "pickup_locker_number": 6, "pickup_locker_code": "LK-A1-6" }
]
}
]
}
}
}
已投櫃 — 投櫃確認、包裹放入櫃中後觸發。載荷中包含收件人取件碼。每個格口還帶 pickup_locker_code,即收件人要去的「{貨架編碼}-{格口號}」標籤;格口沒有格口號時為 null。透過遠程開門 API 打開的櫃門,櫃機回報全部已開格口關門後由平台自動結算,因此無需呼叫 confirm 也會觸發本事件;confirmed_by 標明結算路徑:courier_terminal、partner_api、door_close、timeout_door_closed 或 console。
載荷範例
{
"event_id": "PLE-01JABCDEF",
"event_type": "partner_locker.delivery.delivered",
"livemode": true,
"occurred_at": "2026-07-21T14:30:00-04:00",
"data": {
"delivery_no": "PLD-100234",
"external_order_id": "EXT-9001",
"status": "delivered",
"pickup_status": "ready",
"requested_package_count": 2,
"actual_package_count": 2,
"failed_package_count": 0,
"picked_up_package_count": 0,
"partial_delivery": false,
"delivery_mode": "standard",
"confirmed_by": "door_close",
"split_from_delivery_no": null,
"split_delivery_no": null,
"pickup_code": "483920",
"location": { "id": 12, "name": "Main St Locker" },
"packages": [
{ "sequence": 1, "external_package_id": "PKG-1", "status": "confirmed", "shelf_id": 21, "grid_id": 5011, "compartment_number": "12", "pickup_locker_number": 3, "pickup_locker_code": "LK-A1-3" },
{ "sequence": 2, "external_package_id": "PKG-2", "status": "confirmed", "shelf_id": 21, "grid_id": 5014, "compartment_number": "15", "pickup_locker_number": 6, "pickup_locker_code": "LK-A1-6" }
]
}
}
已取件 — 收件人取走已寄存包裹後觸發。
載荷範例
{
"event_id": "PLE-01JABCDEG",
"event_type": "partner_locker.pickup.completed",
"livemode": true,
"occurred_at": "2026-07-22T09:12:00-04:00",
"data": {
"delivery_no": "PLD-100234",
"status": "picked_up",
"pickup_status": "completed",
"picked_up_package_count": 2
}
}
投遞失敗 — 投遞失敗時觸發;包含每個包裹的失敗代碼。
載荷範例
{
"event_id": "PLE-01JABCDEH",
"event_type": "partner_locker.delivery.failed",
"livemode": true,
"occurred_at": "2026-07-21T14:35:00-04:00",
"data": {
"delivery_no": "PLD-100235",
"status": "failed",
"failed_package_count": 1,
"packages": [
{ "sequence": 1, "status": "failed", "failure_code": "NO_CAPACITY" }
]
}
}
已過期 — 未使用的送件碼或未取件的寄存超過有效期時觸發。
載荷範例
{
"event_id": "PLE-01JABCDEI",
"event_type": "partner_locker.delivery.expired",
"livemode": true,
"occurred_at": "2026-07-24T00:05:00-04:00",
"data": {
"delivery_no": "PLD-100236",
"status": "expired"
}
}
已取消 — 投遞在完成前被取消時觸發。
載荷範例
{
"event_id": "PLE-01JABCDEJ",
"event_type": "partner_locker.delivery.cancelled",
"livemode": true,
"occurred_at": "2026-07-21T15:00:00-04:00",
"data": {
"delivery_no": "PLD-100237",
"status": "cancelled"
}
}
修正重開門 — 在糾錯視窗內重新打開已寄存格口以修正放錯位置時觸發——可在櫃機螢幕或透過 API 操作。correction 區塊中列出被重開的格口。每個格口還帶 pickup_locker_code,即收件人要去的「{貨架編碼}-{格口號}」標籤;格口沒有格口號時為 null。
載荷範例
{
"event_id": "PLE-01JABCDEK",
"event_type": "partner_locker.delivery.correction_reopened",
"livemode": true,
"occurred_at": "2026-07-21T14:31:05-04:00",
"data": {
"delivery_no": "PLD-100234",
"status": "delivered",
"correction": {
"channel": "device",
"reopened_count": 2,
"failed_count": 0,
"compartments": [
{ "compartment_number": "A03", "package_sequence": 1, "grid_id": 5011, "pickup_locker_number": 3, "pickup_locker_code": "LK-A1-3" },
{ "compartment_number": "A04", "package_sequence": 2, "grid_id": 5014, "pickup_locker_number": 6, "pickup_locker_code": "LK-A1-6" }
]
}
}
}
partner_locker_delivery.event_type_code_rotated — 合作夥伴更換投遞的送件碼或取件碼時觸發。rotation 資料塊說明換的是哪種碼、何時更換、是否重發了收件人通知——新碼本身絕不透過 webhook 傳遞,只在換碼 API 的直接回應中揭示一次。
載荷範例
{
"event_id": "PLE-01JABCDEM",
"event_type": "partner_locker.delivery.code_rotated",
"livemode": true,
"occurred_at": "2026-08-11T09:15:00-04:00",
"data": {
"delivery_no": "PLD-100234",
"external_order_id": "EXT-9001",
"status": "active",
"rotation": {
"code_type": "access",
"rotated_at": "2026-08-11T09:15:00-04:00",
"recipient_notified": false
}
}
}
簽章與驗證: 供應商智慧櫃 webhook 使用獨立的簽名方案:X-Webhook-Signature 為 base64(HMAC-SHA256(金鑰, 時間戳 + "\n" + 投遞 ID + "\n" + 原始報文)),時間戳與投遞 ID 取自 X-Webhook-Timestamp 和 X-Webhook-Delivery-Id 請求標頭。同時請校驗 X-Webhook-Content-Digest(報文的 SHA-256)並拒絕過期時間戳。X-Webhook-Id 在重試之間保持不變——可用於冪等去重。
如何設定: 端點在「第三方配送 → 供應商智慧櫃 → 設定」中管理,每個供應商一個端點,可勾選事件列表。投遞失敗按指數退避最多重試 7 次後進入死信;死信事件可在事件頁面手動重發。
沙盒(模擬櫃機)事件: 針對模擬櫃機建立的投遞會產生與正式環境完全相同的 Webhook 事件,並使用相同金鑰簽章,方便您以真實流量進行開發串接。沙盒事件透過三種方式標記:酬載中攜帶 "livemode": false,event_id 以 PLE-MOCK- 開頭,請求標頭包含 X-Webhook-Test: 1。若端點設定了沙盒網址,沙盒事件將傳送到該網址而非正式網址;未設定時回退到正式網址,但仍帶有標記。「傳送沙盒事件」開關可完全停止沙盒事件的外送。
第三方配送事件
推送給第三方配送服務商(快遞商)的包裹配送 Webhook。涵蓋配送任務的整個生命週期,快遞商不必再輪詢新任務。此類別與下方的智能櫃事件相互獨立:每個服務商按類別分別設定獨立的端點、簽名密鑰和事件訂閱——可在其自己的服務商入口網站中設定,也可由平台營運方設定。
任務已建立 — 當訂單被指派給服務商時觸發——無論是透過自動規則還是手動指派。載荷包含任務編號、訂單識別碼和包裹追蹤號。
載荷範例
{
"event_id": "TPDE-01JABCDEA",
"event_type": "delivery.assignment.created",
"occurred_at": "2026-07-30T09:15:00-04:00",
"data": {
"assignment_no": "TPD20260730091500ABC123",
"assignment_status": "assigned",
"order_id": 88231,
"order_ref": "SO-100234",
"packages": [
{ "tracking_number": "TRK-1001", "external_tracking_number": "EXT-1001" }
]
}
}
包裹已移交 — 當倉庫將該任務的全部包裹實際移交給服務商時觸發。
載荷範例
{
"event_id": "TPDE-01JABCDEB",
"event_type": "delivery.assignment.handed_over",
"occurred_at": "2026-07-30T14:02:00-04:00",
"data": {
"assignment_no": "TPD20260730091500ABC123",
"assignment_status": "accepted",
"order_id": 88231,
"order_ref": "SO-100234",
"external_order_number": "CARRIER-556",
"packages": [
{ "tracking_number": "TRK-1001", "external_tracking_number": "EXT-1001" }
]
}
}
任務已取消 — 當平台從服務商處撤回任務時觸發。reason 欄位區分 cancelled(任務在承運商處被取消)、fallback_to_self_delivery(平台將訂單收回自行配送)和 reassigned(訂單已改派給其他服務商)。
載荷範例
{
"event_id": "TPDE-01JABCDEC",
"event_type": "delivery.assignment.cancelled",
"occurred_at": "2026-07-30T16:40:00-04:00",
"data": {
"assignment_no": "TPD20260730091500ABC123",
"assignment_status": "cancelled",
"order_id": 88231,
"order_ref": "SO-100234",
"reason": "cancelled"
}
}
部分送達 — 當一票貨件中部分包裹已送達、其餘仍在途時觸發。packages 陣列給出每個包裹的結果,legs 列出在承運商處下的第三方訂單——承運商不支援一票多件時,每個包裹一個訂單。
載荷範例
{
"event_id": "TPDE-01JABCDED",
"event_type": "delivery.assignment.partial_delivered",
"occurred_at": "2026-07-31T11:20:00-04:00",
"data": {
"assignment_no": "TPD20260730091500ABC123",
"assignment_status": "accepted",
"order_id": 88231,
"order_ref": "SO-100234",
"packages": [
{ "tracking_number": "TRK-1001", "outcome": "delivered" },
{ "tracking_number": "TRK-1002" }
],
"legs": [
{ "leg_no": 1, "package_id": 4451, "external_order_number": "CARRIER-556", "push_status": "pushed", "status": "accepted", "outcome": "delivered" },
{ "leg_no": 2, "package_id": 4452, "external_order_number": "CARRIER-557", "push_status": "pushed", "status": "accepted" }
]
}
}
簽章與驗證: 第三方配送 Webhook 使用與供應商智慧櫃 webhook 相同的簽名方案:X-Webhook-Signature 為 base64(HMAC-SHA256(secret, timestamp + "\n" + delivery id + "\n" + raw body)),其中 timestamp 和 delivery id 取自 X-Webhook-Timestamp 和 X-Webhook-Delivery-Id 請求標頭。同時請校驗 X-Webhook-Content-Digest(報文的 SHA-256)並拒絕過期時間戳。X-Webhook-Id 在重試之間保持不變——可用於冪等去重。
如何設定: 服務商可在服務商入口網站(Webhook 設定)中自行設定該端點,平台營運方也可在「第三方配送 → 服務商 → Webhook」中設定。每個服務商一個端點,可勾選事件列表。簽名密鑰可自動產生,也可自訂設定,並可在設定頁面查看。投遞失敗按指數退避最多重試 7 次後進入死信;死信事件可手動重發。設定頁面可隨時發送帶簽名的測試(mock)事件——測試請求攜帶 X-Webhook-Test: 1 請求標頭,且 payload data 中包含 "test": true。
訂單生命週期事件
在經典的 order.status_change Webhook(保持不變)之外提供的細粒度、可選事件:分配給了誰、司機是否接受、包裹何時被取件、在途、送達或失敗,以及司機上下班變化和經過節流的司機位置。在您設定下方 URL 之前不會發送任何內容。
訂單已分配給司機(手動、路線規劃或自動分配)。由編排器分配時 data.source = auto_assign。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.assigned",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 16,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"source": "auto_assign",
"previous_driver_id": null
}
}
訂單失去了司機(交接、撤銷、拒絕、逾時)。data.previous_driver_id 表示此前是誰持有。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.unassigned",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 16,
"route_id": 5120,
"driver_id": null,
"driver_name": "Alex",
"customer_id": 77,
"previous_driver_id": 42,
"source": "driver_duty_release"
}
}
司機在 App 中接受了自動分配的訂單(需要接單的司機上下班模式)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.accepted",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 16,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"reason": null
}
}
司機拒絕了已分配的訂單;data.reason 攜帶可選的自由文字原因。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.rejected",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 16,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"reason": "Too far away"
}
}
司機已開始取件(狀態 開始取貨 / 取貨中)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.pickup_started",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 45,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"previous_status_id": 16
}
}
包裹已被取件(狀態 已取件)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.picked_up",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 7,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"previous_status_id": 45
}
}
包裹正在送往收件人途中(狀態 開始送貨 / 配送中)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.on_the_way",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 46,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"previous_status_id": 7
}
}
配送成功(狀態 遞送成功)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.completed",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 8,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"previous_status_id": 46
}
}
配送嘗試失敗(稍後重派、需重新安排、收件人拒收)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.failed",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 9,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"previous_status_id": 46
}
}
訂單已取消。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.cancelled",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 12,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"previous_status_id": 16
}
}
員工(若允許,司機也可以)將訂單標記為可取件(調度選項 → 可取件)。
載荷範例
{
"event_id": "OLE-01JABCDE",
"event_type": "order.ready",
"livemode": true,
"occurred_at": "2026-08-17T10:00:05-04:00",
"data": {
"order_id": 88231,
"order_ref": "SO-100234",
"tracking_number": "TRK-1001",
"type": "D",
"status_id": 16,
"route_id": 5120,
"driver_id": 42,
"driver_name": "Alex",
"customer_id": 77,
"ready_by_user_id": 3
}
}
司機在 App 中上班或下班(司機上下班選項)。
載荷範例
{
"event_id": "OLE-01JABCDF",
"event_type": "driver.on_duty_changed",
"livemode": true,
"occurred_at": "2026-08-17T08:00:00-04:00",
"data": {
"driver_id": 42,
"driver_name": "Alex",
"on_duty": true,
"changed_at": "2026-08-17T08:00:00-04:00"
}
}
來自 App 或追蹤器的司機位置,按司機透過 driver_location_min_interval_sec 節流(預設 60 秒)。僅發送到 driver_location_webhook_url。
載荷範例
{
"event_id": "OLE-01JABCDG",
"event_type": "driver.location_update",
"livemode": true,
"occurred_at": "2026-08-17T10:00:30-04:00",
"data": {
"driver_id": 42,
"driver_name": "Alex",
"latitude": 45.5017,
"longitude": -73.5673,
"recorded_at": "2026-08-17T10:00:30-04:00",
"route_id": 5120,
"heading": 90,
"speed": 12.5
}
}
如何設定: 設定 → Webhook(或 GET/PUT /api/v1/webhook-settings、GraphQL webhookSettingsUpdate):order_lifecycle_webhook_url 接收所有 order.* 事件和 driver.on_duty_changed;order_lifecycle_events 可將其收窄為逗號分隔的清單;driver_location_webhook_url 和 driver_location_min_interval_sec 控制 driver.location_update。多個 URL 可用逗號分隔。投遞記錄會以 reference_type order / driver 顯示在 Webhook 投遞日誌中。
簽章與驗證: 簽章方式與您帳戶的其他所有出站 Webhook 完全一致:傳統的 Signature 請求標頭,加上使用您的 webhook_sign_secret 產生的 X-Webhook-Id / X-Webhook-Timestamp / X-Webhook-Signature-V2。重試會重複使用同一個 event_id——請據此去重。
設備訂單事件
針對由您的智慧櫃、自助終端和智慧投遞箱處理之包裹的可選事件:包裹存入設備、被取走、被工作人員取出或超過取件期限,以及該包裹上異常的建立與解決。純新增——現有 Webhook 均不變,設定 device_order_webhook_url 之前不會傳送任何內容。
包裹已放入設備,正在等待下一位處理人(收件人、快遞員或營運人員,見 data.device_order.next_actor)。due_at 為取件截止時間。
載荷範例
{
"event_id": "DOE-90017",
"event_type": "device_order.stored",
"livemode": true,
"occurred_at": "2026-09-15T10:06:00-04:00",
"data": {
"device_order": {
"id": 5012,
"kind": "shipout_pickup",
"status": "stored",
"next_actor": "recipient",
"device_type": "smart_locker",
"device_id": 11,
"device_name": "LOCKER-A",
"grid_code": "A-12",
"reference_number": "SR100234",
"order_id": 88231,
"external_order_id": null,
"due_at": "2026-09-18T10:06:00-04:00",
"overdue_at": null,
"stored_at": "2026-09-15T10:06:00-04:00",
"ended_at": null,
"removal_reason": null
}
}
}
包裹已被其等待的人取走——收件人、快遞員,或清空智慧投遞箱的工作人員。
載荷範例
{
"event_id": "DOE-90017",
"event_type": "device_order.collected",
"livemode": true,
"occurred_at": "2026-09-15T10:06:00-04:00",
"data": {
"device_order": {
"id": 5012,
"kind": "shipout_pickup",
"status": "collected",
"next_actor": "nobody",
"device_type": "smart_locker",
"device_id": 11,
"device_name": "LOCKER-A",
"grid_code": "A-12",
"reference_number": "SR100234",
"order_id": 88231,
"external_order_id": null,
"due_at": "2026-09-18T10:06:00-04:00",
"overdue_at": null,
"stored_at": "2026-09-15T10:06:00-04:00",
"ended_at": "2026-09-16T18:40:00-04:00",
"removal_reason": null
}
}
}
工作人員將包裹從設備中取出。removal_reason 說明原因:overdue_return、handover、relay、anomaly 或 recovery。
載荷範例
{
"event_id": "DOE-90017",
"event_type": "device_order.removed",
"livemode": true,
"occurred_at": "2026-09-15T10:06:00-04:00",
"data": {
"device_order": {
"id": 5012,
"kind": "shipout_pickup",
"status": "removed",
"next_actor": "nobody",
"device_type": "smart_locker",
"device_id": 11,
"device_name": "LOCKER-A",
"grid_code": "A-12",
"reference_number": "SR100234",
"order_id": 88231,
"external_order_id": null,
"due_at": "2026-09-18T10:06:00-04:00",
"overdue_at": "2026-09-18T10:15:00-04:00",
"stored_at": "2026-09-15T10:06:00-04:00",
"ended_at": "2026-09-19T09:00:00-04:00",
"removal_reason": "overdue_return"
}
}
}
包裹超過 due_at 仍未被取走。包裹仍在設備中,取件碼依然有效;overdue_at 會被設定,next_actor 變為 operator。
載荷範例
{
"event_id": "DOE-90017",
"event_type": "device_order.overdue",
"livemode": true,
"occurred_at": "2026-09-15T10:06:00-04:00",
"data": {
"device_order": {
"id": 5012,
"kind": "shipout_pickup",
"status": "stored",
"next_actor": "operator",
"device_type": "smart_locker",
"device_id": 11,
"device_name": "LOCKER-A",
"grid_code": "A-12",
"reference_number": "SR100234",
"order_id": 88231,
"external_order_id": null,
"due_at": "2026-09-18T10:06:00-04:00",
"overdue_at": "2026-09-18T10:15:00-04:00",
"stored_at": "2026-09-15T10:06:00-04:00",
"ended_at": null,
"removal_reason": null
}
}
}
該包裹的處理上建立了一個異常(例如 door_left_open、deposit_unverified、item_missing、overdue)。data.exception 包含 id、type、severity 與 status。
載荷範例
{
"event_id": "DOE-90017",
"event_type": "device_order.exception_opened",
"livemode": true,
"occurred_at": "2026-09-15T10:06:00-04:00",
"data": {
"device_order": {
"id": 5012,
"kind": "shipout_pickup",
"status": "stored",
"next_actor": "recipient",
"device_type": "smart_locker",
"device_id": 11,
"device_name": "LOCKER-A",
"grid_code": "A-12",
"reference_number": "SR100234",
"order_id": 88231,
"external_order_id": null,
"due_at": "2026-09-18T10:06:00-04:00",
"overdue_at": null,
"stored_at": "2026-09-15T10:06:00-04:00",
"ended_at": null,
"removal_reason": null
},
"exception": {
"id": 311,
"type": "door_left_open",
"severity": "attention",
"status": "open",
"resolution_action": null
}
}
}
有人關閉了該包裹處理上的異常。data.exception.status 為 resolved 或 dismissed,resolution_action 說明所做的處理。
載荷範例
{
"event_id": "DOE-90017",
"event_type": "device_order.exception_resolved",
"livemode": true,
"occurred_at": "2026-09-15T10:06:00-04:00",
"data": {
"device_order": {
"id": 5012,
"kind": "shipout_pickup",
"status": "stored",
"next_actor": "recipient",
"device_type": "smart_locker",
"device_id": 11,
"device_name": "LOCKER-A",
"grid_code": "A-12",
"reference_number": "SR100234",
"order_id": 88231,
"external_order_id": null,
"due_at": "2026-09-18T10:06:00-04:00",
"overdue_at": null,
"stored_at": "2026-09-15T10:06:00-04:00",
"ended_at": null,
"removal_reason": null
},
"exception": {
"id": 311,
"type": "door_left_open",
"severity": "attention",
"status": "resolved",
"resolution_action": "marked_ok"
}
}
}
載荷範例: data.device_order:id, kind, status, next_actor, device_type, device_id, device_name, grid_code, reference_number, order_id, external_order_id, due_at, overdue_at, stored_at, ended_at, removal_reason(時間為 ISO 8601 格式,尚未發生時為 null)。異常事件另含 data.exception:id、type、severity、status、resolution_action。取件碼永遠不會包含在內。event_id 為 DOE-<帳本事件 id>,重試時保持不變。
如何設定: 設定 → Webhooks(或 GET/PUT /api/v1/webhook-settings):device_order_webhook_url 接收所有 device_order.* 事件;device_order_events 可將其限定為逗號分隔的清單。多個 URL 可用逗號分隔。投遞紀錄顯示於 Webhook 投遞日誌中,reference_type 為 device_order。
簽章與驗證: 簽章方式與您帳戶的其他所有出站 Webhook 完全一致:傳統的 Signature 請求標頭,加上使用您的 webhook_sign_secret 產生的 X-Webhook-Id / X-Webhook-Timestamp / X-Webhook-Signature-V2。重試會重複使用同一個 event_id——請據此去重。
合作承運商以其自有帳戶投遞至您櫃中的包裹不會透過此通道傳送;合作方透過其供應商智慧櫃 Webhook 接收這些事件。
如何設定
可在兩個層級設定:商戶級(涵蓋全部)或客戶級(覆寫特定 B2B 子帳號)。
1. 進入設定
登入後前往「設定 → API 與 Webhook」。客戶級覆寫在客戶詳情頁。
2. 設定簽章密鑰
至少 16 字元,建議 32 位元組以上隨機字串,接收端用它驗證簽章。
3. 填您需要的事件 URL
只填您關心的事件 URL,其餘留空即可。
webhook_sign_secret您在設定頁配置共用密鑰,每筆出站 webhook 皆以此簽章。接收端用相同密鑰重新計算並比對 — 一致代表內容真實未經竄改。
order_create_webhook_url本地配送訂單(送貨 D / 取件 P / 點對點 P2P)建立時觸發,涵蓋所有入口:網頁表單、REST/GraphQL API、電商平台同步、自動規則、匯入列等。Label-service 及其他非配送類型訂單不觸發。若同一收件方同時設定了 order_create_async_postback_url,批次匯入流程中將略過此 webhook(由批次回呼統一通知)。對應欄位 order_create_webhook_url。
order_status_change_webhook_url訂單狀態每次變更時觸發 — 取件、運送中、已送達、異常、取消等。對應欄位 order_status_change_webhook_url。
track_event_webhook_url包裹生命週期事件觸發時(提交資訊、開始配送、配送成功、未送達等)。對應欄位 tracking_event_webhook_url。「已送達」與「已取件」事件還會附帶簽收憑證:proof_files 以及 proof_files_detail(file_id、type、url、full_url、帶簽章的下載網址)。事件之後才上傳的照片會透過 pod.files_updated 推送。每個檔案還附帶對應事件資訊:tracking_event_id、tracking_event_status_id、tracking_event_key、service_type(1 = delivery / 2 = pickup)和 service_status(1 = success / 2 = failed);未記錄事件的歷史檔案這些欄位為 null。
order_create_async_postback_url批次訂單匯入處理完畢後觸發一次,載荷含每列處理結果。對應欄位 order_create_async_postback_url。
pod_files_webhook_url簽收照片或簽名被新增、取代或刪除時觸發(action:added / updated / removed)——每個檔案一次推送,無需再輪詢附件。設定 pod_files_webhook_url 後啟用。每個檔案還附帶對應事件資訊:tracking_event_id、tracking_event_status_id、tracking_event_key、service_type(1 = delivery / 2 = pickup)和 service_status(1 = success / 2 = failed);未記錄事件的歷史檔案這些欄位為 null。
order_deleted_webhook_url訂單被永久刪除時觸發,便於你的系統同步移除。設定 order_deleted_webhook_url 後啟用。
order_cancel_failed_webhook_url取消請求被拒絕時觸發(例如訂單已在派送中),便於你的營運管道監控失敗的取消,無需輪詢 API 回應。設定 order_cancel_failed_webhook_url 後啟用。
route_board_webhook_url路線看板席位易主或看板狀態變化時觸發——action 欄位說明發生了什麼(claimed、standby、pooled、promoted、withdrawn、vetoed、replaced、assigned、awarded、lost、displaced、settled、board_opened、board_closed、board_cancelled)。僅限企業層級設定。透過 route_board_webhook_url 訂閱。
device_order_webhook_url針對由您的智慧櫃、自助終端和智慧投遞箱處理之包裹的可選事件:包裹存入設備、被取走、被工作人員取出或超過取件期限,以及該包裹上異常的建立與解決。純新增——現有 Webhook 均不變,設定 device_order_webhook_url 之前不會傳送任何內容。
整合更新指南
API 與 Webhook 的全部新能力——冪等取消、對帳拉取、v2 簽章、新事件——附可直接複製的範例,全部向前相容。
簽章與驗證
每筆出站 webhook 在請求標頭附帶 HMAC-SHA256(十六進位)簽章。接收端須以共用密鑰對原始請求主體重新計算並比對,不符即拒絕。
驗證步驟
在任何解析或中介軟體改動前,讀取原始請求主體。
使用 hash_hmac('sha256', rawBody, sharedSecret) 計算簽章並以十六進位輸出。
以恆定時間比較與 Signature 標頭比對(PHP 用 hash_equals,Node 用 crypto.timingSafeEqual)。
簽章符合才回應 2xx,否則回 401。
PHP
Node.js
Python
Go
紅寶石
<?php
$rawBody = file_get_contents('php://input');
$received = $_SERVER['HTTP_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $rawBody, $sharedSecret);
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Bad signature');
}
$payload = json_decode($rawBody, true);
// ... handle event ...
http_response_code(200);
echo 'ok';
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use('/webhooks/superroute', express.raw({ type: 'application/json' }), (req, res) => {
const received = req.header('Signature') || '';
const expected = crypto.createHmac('sha256', sharedSecret)
.update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return res.status(401).send('Bad signature');
}
const payload = JSON.parse(req.body.toString());
// ... handle event ...
res.status(200).send('ok');
});
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhooks/superroute', methods=['POST'])
def webhook():
received = request.headers.get('Signature', '')
expected = hmac.new(shared_secret.encode(), request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(received, expected):
abort(401)
payload = request.get_json()
# ... handle event ...
return 'ok', 200
func handleWebhook(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
received := r.Header.Get("Signature")
mac := hmac.New(sha256.New, []byte(sharedSecret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(received), []byte(expected)) {
w.WriteHeader(401); return
}
// ... handle event ...
w.WriteHeader(200)
w.Write([]byte("ok"))
}
require 'openssl'
require 'rack/utils'
post '/webhooks/superroute' do
raw = request.body.read
received = request.env['HTTP_SIGNATURE'] || ''
expected = OpenSSL::HMAC.hexdigest('sha256', shared_secret, raw)
halt 401 unless Rack::Utils.secure_compare(received, expected)
payload = JSON.parse(raw)
# ... handle event ...
status 200
'ok'
end
重試與可靠性
接收端應快速回應 2xx。若回非 2xx、逾時或不可達,我們會重試投遞。
退避策略
指數退避 — 約 10s、100s、1000s、10000s
請實作冪等. 同一事件可能被重試 — 您可能收到多次。請以訂單號 / 追蹤號做去重鍵,已處理 ID 至少快取 24 小時。
建議回應方式. 快速回 HTTP 200,再非同步處理實際業務。勿在 webhook 處理函式內做慢操作 — 會撞到 3 秒逾時。
簽章驗證工具
貼上您收到的載荷、Signature 標頭值與您的密鑰 — 工具在瀏覽器本地重新計算(資料不會離開本頁)並回報是否一致。
發送測試 Webhook
從我們伺服器真實發出一筆帶簽章的 webhook 到您指定的 URL。用以驗證接收端可達、載荷可解析、簽章驗證正確。
HTTP 狀態碼 / 回應耗時
已發送簽章
回應主體
最近的 Webhook 投遞記錄
查看您帳戶最近的 webhook 投遞嘗試 — 包含真實生產事件與本頁送出的測試。貼上 Bearer Token 載入。
Bearer Token
事件類型
全部
order.created
order.status_change
tracking.event
order.create_async
pod.files_updated
order.deleted
order.cancel_failed
狀態
全部
僅成功
僅失敗
載入紀錄
時間
事件
URL
狀態
HTTP
嘗試
耗時(ms)
測試?
操作
尚無 webhook 投遞記錄。
最佳實務
快速回 HTTP 200,業務處理放到非同步以避開 3 秒逾時。
信任載荷前務必先驗章。
以「至少一次」語義處理事件 — 依訂單號 / 追蹤號去重。
使用 HTTPS 接收端點與有效憑證。
記錄收到的請求,便於 handler 有 bug 時回放。