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
- Chuẩn bị một endpoint HTTPS công khai nhận
POSTvới body JSON. - Lấy API Token của tài khoản gắn với customer của Quý khách.
- 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.
- 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.
- Gọi API gửi thử hoặc bấm Gửi test, rồi kiểm tra Lịch sử giao.
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.
| Method | Endpoint | Chức năng |
|---|---|---|
GET | /api/customers/webhook | Xem cấu hình hiện tại |
PUT / PATCH | /api/customers/webhook | Cập nhật URL, bật/tắt hoặc tạo secret mới |
POST | /api/customers/webhook/test | Gửi một webhook thử |
GET | /api/customers/webhook/deliveries | Xem 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ường | Kiểu | Ý nghĩa |
|---|---|---|
webhook_url | string / null | URL 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_enabled | boolean | Bật hoặc tắt webhook |
regenerate_secret | boolean | Truyề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}'
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ính | Giá trị |
|---|---|
| Method | POST |
| URL | URL webhook Quý khách đã cấu hình |
| Body | JSON, UTF-8 |
| Timeout mỗi lần gửi | 15 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-Event | Tên sự kiện |
X-Bell-Delivery | ID delivery; giữ nguyên giữa các lần gửi lại, dùng để chống xử lý trùng |
X-Bell-Signature | Chữ 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
| Event | Khi 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.test | Quý khách gọi API gửi thử hoặc bấm Gửi test |
fwd.status_changed | Hồ sơ Forwarding thay đổi trạng thái công khai |
fwd.tracking.created | Hồ 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ường | Kiểu | Ý nghĩa |
|---|---|---|
event | string | Tên sự kiện |
timestamp | string | Thờ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_code | string / null | Mã đơn BELL |
data.tracking_code | string / null | Mã tracking của nhà vận chuyển |
data.order_status | integer / null | Mã 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.id | integer / null | ID mốc hành trình |
data.journey.code | string / null | Mã sự kiện hành trình, ví dụ SCN; có thể khác nhau theo nguồn vận chuyển |
data.journey.type | string / null | Loại hành trình, ví dụ START, SHIP, END |
data.journey.note | string / null | Nội dung/mô tả hành trình |
data.journey.city | string / null | Thành phố |
data.journey.state | string / null | Bang/tỉnh |
data.journey.country | string / null | Quốc gia |
data.journey.postcode | string / null | Mã bưu chính; giữ kiểu chuỗi để không mất số 0 ở đầu |
data.journey.date | string / null | Thờ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.
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.testchỉ gửi một lần trong request test, không tự retry.- Dùng
X-Bell-Deliverylà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.
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 |
|---|---|
status | Không bắt buộc; lọc pending, success, failed hoặc skipped |
per_page | Số bản ghi mỗi trang; mặc định 20, giới hạn 1–100 |
page | Trang 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 |
|---|---|
pending | Delivery đã được tạo, chưa ghi nhận kết quả gửi |
success | Endpoint nhận đã trả 2xx |
failed | Lầ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ử |
skipped | Bỏ 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
| HTTP | Trường hợp |
|---|---|
400 | Token đã bị thu hồi (Token is Blacklisted) |
401 | Token hết hạn hoặc chưa xác thực được tài khoản |
403 | Token không hợp lệ (Token is Invalid) |
404 | Thiế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 |
422 | URL/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 |
502 | API 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:
- Đảm bảo URL HTTPS công khai và cho phép BELL gửi
POSTvới JSON. - 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.
- Chống trùng theo
X-Bell-Delivery, lưu sự kiện bền vững trước khi trả 2xx. - Phân biệt
webhook.test,order.journey.createdvà các sự kiệnfwd.*; không cập nhật đơn thật từ dữ liệu test. - Gửi test, kiểm tra lịch sử và theo dõi lỗi tại hệ thống nhận.
- Đồ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.