Webhook
Đăng ký URL nhận sự kiện, kiểm tra chữ ký, xử lý gửi lại.
GOCheap gửi POST tới URL của bạn mỗi khi chuyến đổi trạng thái. Bạn tự quản lý webhook qua API, không cần nhờ GOCheap cài đặt.
Đăng ký
curl https://api-stag.gocheap.vn/open-api/v1/webhooks \
-H "Authorization: ApiKey gc_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Hệ thống đặt phòng",
"url": "https://api.example.com/gocheap/webhook",
"events": ["booking.driver_assigned", "booking.completed", "booking.cancelled"]
}'Response có secret dạng whsec_... dùng để kiểm tra chữ ký. Secret chỉ trả về một lần; mất thì xoá webhook và tạo lại.
- URL bắt buộc HTTPS, trỏ tới địa chỉ công khai.
- Tối đa 5 webhook cho mỗi môi trường.
- Có thể thêm tối đa 10 header riêng (
headers: [{ "key": "X-Source", "value": "gocheap" }]). - Sửa, tắt (
is_active: false), xoá quaPATCH/DELETE /webhooks/{webhook_id}.
Gửi thử
curl -X POST https://api-stag.gocheap.vn/open-api/v1/webhooks/wh_8KfQ2mZx/test-event \
-H "Authorization: ApiKey gc_test_your_api_key"GOCheap gửi ngay sự kiện webhook.test có chữ ký thật và trả về kết quả: mã HTTP URL của bạn trả về, thời gian phản hồi, lỗi kết nối nếu có.
Nội dung gửi
POST /gocheap/webhook HTTP/1.1
Content-Type: application/json
User-Agent: GOCheap-Webhook/1.0
X-GoCheap-Event: booking.driver_assigned
X-GoCheap-Delivery: dlv_01JCQ9A2B3
X-GoCheap-Timestamp: 1794469420
X-GoCheap-Signature: 5d41402abc4b2a76b9719d911017c592...{
"id": "evt_01JCQ8Z4M2",
"event": "booking.driver_assigned",
"created_at": "2026-11-12T19:03:40+07:00",
"attempt": 1,
"data": {
"booking_id": "LX261112QKD",
"partner_reference_id": "HTL-20261112-0042",
"status": "driver_assigned",
"price": 385000,
"driver": { "name": "Trần Minh Đức", "phone": "0987654321" }
}
}data luôn là toàn bộ chuyến, cùng cấu trúc với GET /bookings/{booking_id}.
Kiểm tra chữ ký
X-GoCheap-Signature = hex( HMAC-SHA256( secret, X-GoCheap-Timestamp + "." + body gốc ) ).
- Tính trên body gốc (raw bytes), không phải JSON đã parse rồi stringify lại.
- So sánh bằng hàm an toàn thời gian.
- Từ chối nếu timestamp lệch quá 300 giây để chống gửi lại.
Node.js (Express)
const crypto = require('crypto');
app.post('/gocheap/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.header('X-GoCheap-Timestamp');
const sig = req.header('X-GoCheap-Signature') || '';
const expected = crypto
.createHmac('sha256', process.env.GOCHEAP_WEBHOOK_SECRET)
.update(`${ts}.${req.body}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
const valid = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!fresh || !valid) return res.status(401).end();
const event = JSON.parse(req.body);
// bỏ qua nếu event.id đã xử lý, rồi cập nhật đơn theo event.data.status
res.status(200).end();
});PHP
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_GOCHEAP_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_GOCHEAP_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $body, getenv('GOCHEAP_WEBHOOK_SECRET'));
if (abs(time() - (int) $ts) > 300 || !hash_equals($expected, $sig)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
http_response_code(200);Gửi lại khi lỗi
Trả mã 2xx trong 10 giây để xác nhận đã nhận. Không nhận được 2xx (lỗi mạng, timeout, 4xx, 5xx), GOCheap gửi lại:
| Lần | Sau |
|---|---|
| 2 | 1 phút |
| 3 | 5 phút |
| 4 | 30 phút |
| 5 | 2 giờ |
attempt cho biết đây là lần gửi thứ mấy. Sau lần 5 vẫn lỗi thì dừng; dùng GET /bookings để đối soát.
Xử lý đúng
- Bỏ trùng theo
id. Một sự kiện có thể đến hơn một lần. - Không giả định thứ tự. So
created_atđể giữ trạng thái mới nhất. - Trả
2xxtrước, xử lý sau. Việc nặng (gửi email, cập nhật nhiều bảng) nên đẩy vào hàng đợi phía bạn để không quá 10 giây.