Chuyển tới nội dung chính

Webhook hành trình đơn

BELL chủ động gửi HTTP POST tới URL của Quý khách khi đơn hàng có một mốc hành trình mới. Quý khách có thể cập nhật tiến độ trên hệ thống của mình mà không cần liên tục gọi API tra cứu hành trình.

Trang này hướng dẫn cấu hình webhook, nhận sự kiện order.journey.created, xác thực chữ ký và kiểm tra lịch sử gửi. Các sự kiện Forwarding dùng chung URL và secret này.


1. Bắt đầu nhanh​

  1. Chuẩn bị một endpoint HTTPS công khai nhận POST với body JSON.
  2. Lấy API Token của tài khoản gắn với customer của Quý khách.
  3. Cấu hình URL và bật webhook bằng API bên dưới, hoặc vào Preferences → Webhook, mục Webhook hành trình đơn trên phần mềm BELL. Nhập Webhook URL, bật Bật đẩy hành trình về webhook và bấm Lưu.
  4. Lưu secret được trả về hoặc hiển thị sau khi tạo. Cấu hình secret này ở endpoint nhận.
  5. Gọi API gửi thử hoặc bấm Gửi test, rồi kiểm tra Lịch sử giao.
Lưu secret ngay khi tạo

Secret chỉ được trả về khi vừa tạo mới. API xem cấu hình không trả lại secret. Nếu mất secret, hãy tạo secret mới và cập nhật endpoint nhận của Quý khách.

API Token dùng để gọi API cấu hình của BELL; webhook secret dùng để xác thực request BELL gửi tới Quý khách. Đây là hai giá trị khác nhau, không dùng thay thế cho nhau và không chia sẻ công khai.

2. API cấu hình webhook​

Base URL: https://expressbe.bellvn.com.

Các API trong mục này yêu cầu header Authorization: Bearer <token>. BELL xác định customer từ tài khoản của token; Quý khách không cần truyền customer_id.

MethodEndpointChức năng
GET/api/customers/webhookXem cấu hình hiện tại
PUT / PATCH/api/customers/webhookCập nhật URL, bật/tắt hoặc tạo secret mới
POST/api/customers/webhook/testGửi một webhook thử
GET/api/customers/webhook/deliveriesXem lịch sử gửi

Xem cấu hình​

curl 'https://expressbe.bellvn.com/api/customers/webhook' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'

Response mẫu:

{
"status": "success",
"data": {
"customer_id": 12,
"webhook_url": "https://example.com/webhooks/bell",
"webhook_enabled": true,
"has_secret": true
}
}

webhook_url có thể là null khi chưa cấu hình. has_secret cho biết đã có secret hay chưa, không phải giá trị secret.

Cấu hình URL và bật webhook​

curl -X PATCH 'https://expressbe.bellvn.com/api/customers/webhook' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data '{
"webhook_url": "https://example.com/webhooks/bell",
"webhook_enabled": true
}'
TrườngKiểuÝ nghĩa
webhook_urlstring / nullURL hợp lệ, tối đa 500 ký tự. Nên dùng HTTPS. Truyền null để xóa URL khi webhook đã tắt hoặc đồng thời truyền webhook_enabled: false
webhook_enabledbooleanBật hoặc tắt webhook
regenerate_secretbooleanTruyền true để tạo secret mới

Chỉ cần truyền trường muốn thay đổi. Không thể bật webhook nếu chưa có URL. Khi bật webhook mà chưa có secret, BELL tự tạo secret.

Response mẫu khi vừa tạo secret (giá trị secret bên dưới chỉ là minh họa):

{
"status": "success",
"message": "Webhook configuration updated",
"data": {
"customer_id": 12,
"webhook_url": "https://example.com/webhooks/bell",
"webhook_enabled": true,
"has_secret": true,
"secret": "whsec_example_not_a_real_secret"
}
}

Secret thực tế có tiền tố whsec_ và 48 ký tự ngẫu nhiên phía sau. Nếu lần cập nhật không tạo secret mới, data.secret là null; giữ nguyên secret đã lưu.

Tắt webhook hoặc tạo secret mới​

Tắt webhook nhưng giữ URL và secret:

curl -X PATCH 'https://expressbe.bellvn.com/api/customers/webhook' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data '{"webhook_enabled": false}'

Tạo secret mới:

curl -X PATCH 'https://expressbe.bellvn.com/api/customers/webhook' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data '{"regenerate_secret": true}'
Khi đổi URL hoặc secret

BELL dùng URL và secret hiện tại tại thời điểm gửi, kể cả khi gửi lại một delivery cũ. Secret cũ không tiếp tục được dùng để ký request mới. Hãy phối hợp cập nhật endpoint nhận khi đổi secret và gửi test sau khi thay đổi.

3. Gửi webhook thử​

curl -X POST 'https://expressbe.bellvn.com/api/customers/webhook/test' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'

Không cần body. BELL gửi request tới URL đã lưu với event webhook.test, cùng cấu trúc dữ liệu như webhook hành trình nhưng dùng dữ liệu mẫu, không phải đơn thật. Ví dụ: order_code = BELL_TEST_0000000001, journey.id = 0.

API này gửi đồng bộ, không áp dụng cơ chế retry của webhook hành trình. Chỉ cần có URL đã lưu để gửi test, kể cả khi webhook đang tắt; nên tạo secret trước để kiểm tra đầy đủ việc xác thực chữ ký.

Response khi endpoint nhận trả 2xx:

{
"status": "success",
"message": "Test webhook delivered",
"data": {
"delivery_id": 12345,
"http_status": 200
}
}

Nếu endpoint nhận trả khác 2xx hoặc lỗi kết nối/timeout, API BELL trả HTTP 502 và status: "error". data.delivery_id giúp đối chiếu lịch sử; data.http_status có khi đã nhận được HTTP response từ endpoint của Quý khách.

4. Request BELL gửi tới Quý khách​

Thuộc tínhGiá trị
MethodPOST
URLURL webhook Quý khách đã cấu hình
BodyJSON, UTF-8
Timeout mỗi lần gửi15 giây

Headers​

Content-Type: application/json
User-Agent: BellVN-Webhook/1.0
X-Bell-Event: order.journey.created
X-Bell-Delivery: 12345
X-Bell-Signature: sha256=<hex-hmac-sha256>
HeaderÝ nghĩa
X-Bell-EventTên sự kiện
X-Bell-DeliveryID delivery; giữ nguyên giữa các lần gửi lại, dùng để chống xử lý trùng
X-Bell-SignatureChữ ký HMAC-SHA256 trên raw body, có tiền tố sha256=

Request này không dùng API Token của Quý khách làm Bearer Token. Endpoint nhận cần xác thực bằng X-Bell-Signature.

Các sự kiện​

EventKhi nào gửi
order.journey.createdĐơn hàng thuộc customer có một mốc hành trình mới, webhook đang bật và có URL tại thời điểm quét
webhook.testQuý khách gọi API gửi thử hoặc bấm Gửi test
fwd.status_changedHồ sơ Forwarding thay đổi trạng thái công khai
fwd.tracking.createdHồ sơ Forwarding có một mốc tiến độ mới

Hai sự kiện fwd.* có payload riêng, xem Webhook Forwarding. Endpoint dùng chung cần phân biệt các sự kiện theo event, không giả định request nào cũng có data.journey.

5. Payload hành trình đơn​

Mỗi event order.journey.created chứa một mốc hành trình, không phải toàn bộ lịch sử của đơn.

{
"event": "order.journey.created",
"timestamp": "2026-10-08T10:30:00+07:00",
"data": {
"order_code": "BELL0000000123",
"tracking_code": "TRACK123456",
"order_status": 4,
"journey": {
"id": 987654,
"code": "SCN",
"type": "SHIP",
"note": "Arrived at sorting center",
"city": "TAUNTON",
"state": "MASSACHUSETTS",
"country": "US",
"postcode": "02780",
"date": "2026-10-08 10:20:00"
}
}
}
TrườngKiểuÝ nghĩa
eventstringTên sự kiện
timestampstringThời điểm tạo payload, ISO 8601 có múi giờ; không phải thời điểm xảy ra hành trình
data.order_codestring / nullMã đơn BELL
data.tracking_codestring / nullMã tracking của nhà vận chuyển
data.order_statusinteger / nullMã trạng thái số của đơn tại thời điểm tạo payload; không phải chuỗi trạng thái của API chi tiết đơn
data.journey.idinteger / nullID mốc hành trình
data.journey.codestring / nullMã sự kiện hành trình, ví dụ SCN; có thể khác nhau theo nguồn vận chuyển
data.journey.typestring / nullLoại hành trình, ví dụ START, SHIP, END
data.journey.notestring / nullNội dung/mô tả hành trình
data.journey.citystring / nullThành phố
data.journey.statestring / nullBang/tỉnh
data.journey.countrystring / nullQuốc gia
data.journey.postcodestring / nullMã bưu chính; giữ kiểu chuỗi để không mất số 0 ở đầu
data.journey.datestring / nullThời điểm hành trình từ dữ liệu nguồn, thường dạng YYYY-MM-DD HH:mm:ss, không kèm offset múi giờ

Các trường dữ liệu có thể là null khi nguồn thiếu thông tin. Không mặc định journey.date là UTC hoặc lấy offset của timestamp áp cho nó. Khi cần chuẩn hóa thời gian hoặc ánh xạ mã trạng thái, hãy xác nhận quy ước của nguồn vận chuyển với BELL.

6. Xác thực chữ ký​

Tính HMAC-SHA256(raw_body, webhook_secret) dưới dạng hex rồi thêm tiền tố sha256=. So sánh với X-Bell-Signature bằng hàm so sánh an toàn về thời gian.

Dùng đúng raw body

Không parse JSON rồi encode lại để tính chữ ký. Thay đổi khoảng trắng, Unicode, thứ tự trường hoặc cách escape ký tự có thể làm chữ ký không khớp.

Ví dụ PHP dưới đây chỉ minh họa bước xác thực và đọc JSON; chưa bao gồm lưu sự kiện hay chống xử lý trùng:

<?php

$secret = getenv('BELL_WEBHOOK_SECRET');
if (!is_string($secret) || $secret === '') {
http_response_code(500);
exit('Webhook secret is not configured');
}

$rawBody = file_get_contents('php://input');
if ($rawBody === false) {
http_response_code(400);
exit('Cannot read request body');
}

$signature = $_SERVER['HTTP_X_BELL_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}

$payload = json_decode($rawBody, true);
$deliveryId = $_SERVER['HTTP_X_BELL_DELIVERY'] ?? '';
if (!is_array($payload) || json_last_error() !== JSON_ERROR_NONE || $deliveryId === '') {
http_response_code(400);
exit('Invalid payload or missing delivery ID');
}

BELL_WEBHOOK_SECRET là biến cấu hình trên hệ thống nhận của Quý khách, chứa secret BELL đã cấp. Sau bước trên, lưu delivery và sự kiện một cách bền vững, chống trùng theo delivery ID, rồi trả 2xx. Chỉ xác thực chữ ký thành công chưa có nghĩa là đã xử lý nghiệp vụ thành công.

7. Phản hồi, retry và thứ tự sự kiện​

  • Trả HTTP 2xx trong vòng 15 giây sau khi đã lưu bền vững sự kiện hoặc đưa vào hàng đợi xử lý bền vững của Quý khách. Không cần response body theo cấu trúc riêng.
  • Với webhook nghiệp vụ, khi nhận khác 2xx hoặc lỗi kết nối/timeout, BELL tự thử lại. Cấu hình hiện tại là tối đa 5 lần thử tổng cộng, gồm lần đầu và tối đa 4 lần gửi lại; khoảng chờ giữa các lần gửi lại lần lượt là 10 giây, 30 giây, 2 phút, 10 phút. Thời gian thực tế phụ thuộc hàng đợi.
  • webhook.test chỉ gửi một lần trong request test, không tự retry.
  • Dùng X-Bell-Delivery làm khóa chống xử lý trùng với ràng buộc duy nhất hoặc cơ chế tương đương. Nếu đã nhận và lưu delivery, trả 2xx cho lần gửi lại mà không xử lý nghiệp vụ lần nữa.
  • Không đảm bảo sự kiện đến đúng thứ tự thời gian. Không ghi đè trạng thái mới bằng sự kiện cũ chỉ dựa vào thứ tự nhận; khi cần đối chiếu, gọi API tra cứu hành trình.
Phạm vi gửi và dữ liệu cũ

Webhook gửi bất đồng bộ, không cam kết gửi ngay tại thời điểm nhà vận chuyển phát sinh mốc hành trình. Những mốc đã được hệ thống quét khi webhook đang tắt hoặc chưa có URL không tự gửi bù khi bật lại. Webhook không thay thế việc đồng bộ lịch sử ban đầu bằng API tra cứu.

Nếu webhook bị tắt hoặc URL bị xóa trước khi một delivery đang chờ được gửi, delivery đó có thể chuyển sang skipped.

8. Xem lịch sử gửi​

curl 'https://expressbe.bellvn.com/api/customers/webhook/deliveries?status=failed&per_page=20&page=1' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'
QueryÝ nghĩa
statusKhông bắt buộc; lọc pending, success, failed hoặc skipped
per_pageSố bản ghi mỗi trang; mặc định 20, giới hạn 1–100
pageTrang cần lấy

Response có status: "success" và data là đối tượng phân trang. Danh sách nằm trong data.data, metadata gồm data.current_page, data.per_page, data.total, data.last_page. Bản ghi mới nhất được xếp trước.

Mỗi bản ghi có id (tương ứng X-Bell-Delivery), order_id, order_journey_id, event, webhook_url, status, http_status, attempts, last_error, delivered_at, created_at. API này không trả raw payload, response body của endpoint nhận hoặc secret. webhook_url trong lịch sử là URL lúc tạo delivery; nếu đổi URL sau đó, URL thực tế lúc gửi có thể khác.

Trạng tháiÝ nghĩa
pendingDelivery đã được tạo, chưa ghi nhận kết quả gửi
successEndpoint nhận đã trả 2xx
failedLần gửi gần nhất thất bại; có thể vẫn đang chờ retry, không nhất thiết đã hết số lần thử
skippedBỏ qua vì webhook đã tắt, URL không còn hoặc customer không còn tại thời điểm gửi

attempts là số lần đã gửi HTTP. Với sự kiện fwd.*, order_id và order_journey_id là null; nhận diện hồ sơ Forwarding bằng payload theo tài liệu riêng.

9. Lỗi thường gặp và checklist đưa endpoint vào sử dụng​

HTTPTrường hợp
400Token đã bị thu hồi (Token is Blacklisted)
401Token hết hạn hoặc chưa xác thực được tài khoản
403Token không hợp lệ (Token is Invalid)
404Thiếu token (Authorization Token not found) hoặc không tìm thấy customer khi xem/cập nhật cấu hình
422URL/kiểu dữ liệu không hợp lệ; bật webhook khi thiếu URL; không có trường cập nhật; tài khoản chưa gắn customer; gửi test khi chưa có URL
502API gửi test không nhận được 2xx từ endpoint của Quý khách hoặc gặp lỗi kết nối/timeout

Kiểm tra Xác thực API khi gặp lỗi token. Đối với lỗi nghiệp vụ, response thường có status: "error" và message. Lỗi validation có thể trả message và errors theo từng trường; lỗi token có thể chỉ trả thông báo trong status. Không giả định mọi lỗi có cùng cấu trúc.

Trước khi đưa endpoint nhận vào sử dụng:

  1. Đảm bảo URL HTTPS công khai và cho phép BELL gửi POST với JSON.
  2. Lưu secret an toàn và xác thực chữ ký trên raw body trước khi tin tưởng dữ liệu.
  3. Chống trùng theo X-Bell-Delivery, lưu sự kiện bền vững trước khi trả 2xx.
  4. Phân biệt webhook.test, order.journey.created và các sự kiện fwd.*; không cập nhật đơn thật từ dữ liệu test.
  5. Gửi test, kiểm tra lịch sử và theo dõi lỗi tại hệ thống nhận.
  6. Đồng bộ lịch sử ban đầu bằng API tra cứu; liên hệ BELL nếu cần đối chiếu hoặc hỗ trợ một delivery đã gửi lỗi.