Skip to Content
WebhooksTổng quan
View as Markdown

Webhooks — Tổng quan

Webhook là tín hiệu xác thực. Callback trình duyệt (onSuccess) và các view của dashboard là tiện lợi; webhook là sự thật gốc.

Đảm bảo delivery

  • At-least-once. Một event có thể được delivery tối đa 6 lần nếu server của bạn không trả về 2xx trong timeout. Hãy làm handler của bạn idempotent — dedup trên X-Delivery (payload không có trường event_id; UUID delivery ổn định là khóa idempotency).
  • Một event mỗi HTTP request. Không batch.
  • Cách ly theo từng endpoint. Nếu bạn có nhiều endpoint đã đăng ký, mỗi cái có track delivery và retry riêng. Một endpoint chậm không làm chậm các endpoint khác.
  • Đã ký. Mọi payload mang header X-Signature (và trong cửa sổ 24 giờ sau khi rotate, cũng có X-Signature-Prev). Xác thực trước khi làm bất cứ điều gì với body. Xem Xác thực chữ ký.

Các loại event có thể subscribe

EventPhát khi…
payment.settledGiao dịch chuyển on-chain đã đạt số confirmation của chain. Dùng cái này để đánh dấu order đã thanh toán.
payment.failedMột thanh toán fiat bị payment provider từ chối. Không phát cho timeout crypto — những cái đó surface dưới dạng checkout.expired, và thanh toán crypto thiếu surface dưới dạng payment.underpaid.
payment.underpaidVốn đã đến nhưng thiếu so với tổng order (điển hình: phí chuyển stablecoin trừ từ amount).
payment.overpaidVốn đến vượt tổng order. Phần dư được ghi nhận nhưng không auto-refund.
order.createdMột order mới được mở — hoặc bởi lệnh gọi B2B API của bạn hoặc bởi việc chuyển đổi checkout-session.
order.canceledMột order chuyển sang canceled. data.reason của payload phân biệt cancel thủ công với payment_timeout (một order chưa thanh toán đã hết thời gian).
order.resolvedMột order PARTIAL_PAID được resolve sang PAID — merchant chấp nhận phần thiếu.
order.reopenedMột order trước đó auto-canceled (canceled_reason=payment_timeout) được merchant mở lại.
checkout.createdMột người mua đã mở checkout cho một order.
checkout.completedLuồng phía người mua đã kết thúc (không có nghĩa là đã settle on-chain — dùng payment.settled cho điều đó).
checkout.expiredNgười mua đã từ bỏ và TTL của session đã hết.
payment.refund.requestedMột bản ghi refund được tạo — hoặc từ lệnh gọi API do merchant khởi tạo hoặc từ form refund-request do khách hàng submit.
payment.refund.approvedMột refund pending đã vượt qua workflow approve của bạn.
payment.refund.rejectedMột refund pending bị từ chối.
payment.refund.executedGiao dịch chuyển on-chain của refund đã hoàn tất và bản ghi chuyển sang executed terminal.
refund_request.createdMột token refund-request đã được phát hành. data.source là b2b / dashboard / renewal. Tùy chọn subscribe — hữu ích cho pipeline audit theo dõi token nào hiện active theo từng order.
refund_request.renewal_requestedMột người mua bấm “Request new link” sau khi token của họ hết hạn. Khuyến nghị mạnh subscribe — đây là tín hiệu cho merchant rằng widget renewal có item mới để hành động.
refund_request.renewedMột renewal được approve và một token mới thay thế cái cũ. data.old_token / data.new_token tạo thành chuỗi audit.
refund_request.canceledMột merchant đã chuyển một token sang CANCELED từ dashboard (ví dụ từ chối một renewal request, kill một link đang chạy). Idempotent — chỉ transition đầu tiên phát event. data.reason là ghi chú tùy chọn của merchant.

Dự kiến (Sắp ra mắt)

Sắp ra mắt. Các event này thuộc về hóa đơn định kỳ và thuê bao, hiện chưa khả dụng. Chúng không nằm trong bảng có thể subscribe ở trên và hiện chưa thể đăng ký. Xem Hóa đơn định kỳ.

Event dự kiếnPhát khi…
subscription.createdMột subscription được tạo.
invoice.createdMột hóa đơn cho chu kỳ thanh toán được tạo.
invoice.paidMột hóa đơn được thanh toán.
subscription.past_dueMột hóa đơn chưa thanh toán sau ngày đến hạn.
subscription.canceledMột subscription bị hủy.

Form endpoint của dashboard liệt kê cùng các event này. Subscribe đến một event không tồn tại sẽ bị reject khi bạn lưu endpoint.

Event test không thể subscribe. Nút Send Test theo từng endpoint của dashboard gửi một event webhook.test.ping đến đúng một endpoint đó ngay lập tức, không retry. Nó không nằm trong catalog ở trên: bạn nhận nó vì bạn có một endpoint đã đăng ký, không phải vì bạn đã subscribe.

Chỉ subscribe đến các event bạn xử lý. Mỗi endpoint có filter event riêng; wildcard "*" nghĩa là “mọi event, kể cả các event được thêm trong tương lai”. Subscribe ít event hơn giữ handler của bạn đơn giản hơn và có ít retry hơn khi endpoint của bạn gặp lỗi.

Payload + headers

HTTP body chính là object data theo từng event trực tiếp. Không có envelope ngoài kiểu Stripe — các trường như event type, delivery ID, và timestamp phát sống trong header thay vào đó. Với payment.settled body trông như:

{ "receipt_id": "rcp_…", "order_id": "ord_…", "payment_intent_id": "pin_…", "checkout_session_id": "cst_…", "merchant_id": "mer_…", "customer_id": "cus_…", "total": "49.00", "currency": "USD", "payment_method": "crypto", "token": "USDC", "network": "polygon", "tx_hash": "0x…", "deposit_address": "0x…", "treasury_address": "0x…", "amount_received": "49.00", "confirmations": 5, "metadata": { /* theo từng event */ } }

Các event khác mang bộ trường riêng. Tên trường ổn định (lower snake_case); tx hash on-chain luôn là tx_hash.

tx_hash là mã định danh giao dịch theo định dạng riêng của network (0x… trên các chain EVM; hash hoặc chữ ký native trên TRON, Solana và TON). deposit_address có thể vắng mặt với TRON, Solana và TON vì người mua trả thẳng vào ví Treasury của bạn, còn confirmations tuân theo Chains & tài sản.

Header trên request inbound

Content-Type: application/json X-Event: payment.settled X-Delivery: 7c9e6679-7425-40de-944b-e07fc1f90ae7 Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7 X-Timestamp: 1729536000 X-Signature: sha256=9a8b7c… X-Signature-Prev: sha256=fa31b2… (chỉ trong cửa sổ grace rotate)
HeaderLà gì
X-EventLoại event (ví dụ payment.settled). Route trên cái này tại lớp proxy nếu bạn muốn bỏ qua parse JSON.
X-DeliveryUUID nhận diện row delivery. Ổn định qua mọi retry của cùng cặp (event, endpoint) — dùng làm khóa idempotency của bạn.
Idempotency-KeyMirror X-Delivery (cùng giá trị). Set trên mọi delivery.
X-TimestampUnix-seconds khi attempt được gửi. Được ký vào payload để một cặp (body, X-Signature) bị capture không thể replay vô hạn — reject delivery có timestamp ngoài cửa sổ tolerance của bạn.
X-Signaturesha256=<hex> của HMAC-SHA256(secret, X-Timestamp + "." + raw_body). Xem Xác thực chữ ký.
X-Signature-PrevCùng thuật toán với secret trước đó. Chỉ có trong cửa sổ 24 giờ sau khi bạn rotate — cho phép verifier chạy một trong hai key tiếp tục chấp nhận delivery trong cutover. Sau khi cửa sổ đóng, header ngừng được gửi.

Schedule retry

Nếu endpoint của bạn không trả về 2xx trong timeout, chúng tôi retry theo schedule này (timestamp tương đối với attempt đầu):

AttemptDelayTích lũy
10s0s
2+1 min1m
3+5 min6m
4+15 min21m
5+1 hour1h 21m
6+6 hours7h 21m

Sau khi attempt 6 fail, delivery được đánh dấu Failed và email tài khoản của bạn được thông báo. Bạn có thể replay các event failed từ panel Developers → Webhooks → Delivery history trong dashboard. Mỗi lần replay là một delivery mới với X-Delivery riêng.

Đăng ký một endpoint

Từ merchant dashboard :

  1. Developers → Webhooks → + Add endpoint
  2. Paste URL của bạn — chỉ https://… (HTTP thuần bị reject; form tạo cũng chặn localhost, các dải IP riêng, và URL mang userinfo)
  3. Chọn các event để subscribe (hoặc * cho tất cả)
  4. Chọn môi trường — test hoặc live (mỗi cái có secret riêng; chúng không bao giờ qua lại)
  5. Save → dashboard hiển thị secret ký (whsec_…) một lần. Lưu nó phía server; bạn sẽ cần nó cho hai tính năng tiếp theo.

Bạn có thể đăng ký tối đa 10 endpoint mỗi môi trường mỗi merchant (ví dụ một cho fulfillment production, một cho staging mirror, một cho notifier Slack). Mỗi cái có trạng thái retry và secret riêng.

Hành động vòng đời trên mỗi endpoint

Menu ⋮ trên thẻ mỗi endpoint surface:

  • Edit — thay URL, mô tả, hoặc danh sách subscription. URL mới được re-validate với cùng quy tắc https:///SSRF như khi tạo.
  • Send Test — POST đồng bộ một envelope webhook.test.ping ký bằng secret hiện tại của bạn. Dashboard hiển thị HTTP status, latency, và snippet 512 byte của response. Test ping không được retry, nên bạn nhận câu trả lời tức thời.
  • Rotate Secret — sinh secret mới. Cái trước đó vẫn hợp lệ trong 24 giờ (delivery mang cả X-Signature và X-Signature-Prev trong cửa sổ để verifier chạy một trong hai key tiếp tục chấp nhận event trong khi bạn redeploy).
  • Reveal Secret — hiển thị lại secret hiện tại. Gate bởi xác thực 2FA mới và ghi vào audit log; chỉ dùng khi bạn mất copy và Rotate không chấp nhận được.
  • Enable / Disable — bật hoặc tắt endpoint mà không mất lịch sử delivery. Endpoint bị disable vẫn ở trong dashboard nhưng không nhận delivery mới.
  • Delete — vĩnh viễn. Dùng Disable nếu bạn có thể enable lại sau.

Mẹo cho handler

  1. Trả 2xx nhanh. Acknowledge với 200 OK trước khi làm việc nặng — chuyển fulfillment sang background job. Timeout mỗi attempt là 10 giây; giữ response lâu hơn sẽ kích hoạt retry. Timeout ở phía platform và không thể cấu hình theo merchant — liên hệ support nếu handler của bạn thực sự cần thêm thời gian.
  2. Dedup trên X-Delivery (hoặc Idempotency-Key — cùng giá trị). Kể cả khi bạn trả 2xx, một upstream proxy có thể drop kết nối và kích hoạt retry; delivery ID ổn định qua mọi retry của cùng row delivery, nên đó là key đúng.
  3. Khoan dung với loại event không biết. Event mới có thể xuất hiện; trả 200 và no-op thay vì 4xx, nếu không các delivery đó sẽ cứ retry mãi.
  4. Log X-Delivery cạnh business logic của bạn. Khi có gì đó sai, đó là khóa join giữa phía chúng tôi và phía bạn.

Tiếp theo