Lỗi
Mọi response 4xx/5xx đều mang cùng envelope JSON:
{
"code": 400,
"message": "invalid_input",
"details": [
{ "field": "items[0].unit_price", "message": "must be a positive decimal string" }
]
}code— HTTP status dạng số (400,401,404, …). Hữu ích cho xử lý lớp HTTP chung, nhưng cho logic phân nhánh hãy switch trênmessage—codesẽ không phân biệt được giữa, ví dụ,invalid_inputvàpayment_method_not_supported(cả hai đều 400).message— tên sentinel lower-snake-case (ví dụINVALID_INPUTtrở thành"invalid_input"). Ổn định qua các bản phát hành — switch trên cái này.details— được điền cho lỗi validation. Mảng các object{ field, message }để client có thể pin lỗi vào input. Bỏ qua trong các trường hợp khác.
Body lỗi không bao gồm trace ID hay timestamp. Để báo cáo sự cố, hãy
gửi cho support header Date của response, các header X-RateLimit-*,
merchant ID, endpoint, và thời gian request xấp xỉ.
Lỗi xác thực dùng cấu trúc (structure) khác. Envelope ở trên là
cái API trả về cho hầu hết các lỗi. Request bị reject trước khi được
xử lý (X-Signature thiếu hoặc không hợp lệ, X-Client-ID
không được nhận diện, hoặc X-Timestamp cũ trên lệnh gọi /b2b/v1/*)
trả về dưới dạng { "error": "...", "message": "..." }, trong đó
error là slug thô (unauthorized / bad_request /
service_unavailable) và message mang chi tiết. Không có code
dạng số và không có details. Hãy phân nhánh trên HTTP status
trước, rồi đọc message. Ví dụ (401):
{ "error": "unauthorized", "message": "invalid signature" }HTTP status → mã điển hình
| HTTP | Các giá trị code điển hình | Ý nghĩa |
|---|---|---|
| 400 | INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUM | Bad request — xem details |
| 401 | INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); SESSION_EXPIRED (chỉ dashboard) | Auth fail — key sai, timestamp hết hạn, chữ ký sai. Các mã đăng nhập dashboard không liên quan đến tích hợp B2B. |
| 402 | INSUFFICIENT_CREDIT | Số dư prepaid của merchant đã hết — top up trước khi retry |
| 403 | FORBIDDEN, IP_BLOCKED | Key hợp lệ nhưng thiếu scope/cấp phép IP cho lệnh gọi này |
| 404 | NOT_FOUND, RECORD_NOT_FOUND | Resource không tồn tại (hoặc không tồn tại cho merchant này) |
| 409 | ALREADY_EXISTS | Replay idempotency với body khác, hoặc state machine từ chối transition |
| 429 | TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTS | Đạt rate limit; back off và retry |
| 500 | INTERNAL_SERVER_ERROR, EXTERNAL_SERVICE_ERROR | Lỗi của chúng tôi; an toàn để retry với backoff. |
| 503 | SERVICE_UNAVAILABLE | Một dependency downstream đang down. Retry với backoff |
Tham chiếu mã đầy đủ
Các giá trị code bạn có thể thấy:
Xác thực (401)
INVALID_CREDENTIALS— combo API key bị rejectINVALID_TOKEN— token không parse được hoặc bị tamperTOKEN_EXPIRED— token đã hết hạnSESSION_EXPIRED— session dashboard đã hết hạnINVALID_SIGNATURE— chữ ký HMAC không khớp trên lệnh gọi B2B / webhook
Authorization (403)
FORBIDDEN— đã xác thực, nhưng bạn không được phép thực hiện hành động nàyIP_BLOCKED— request từ địa chỉ IP này bị chặn
Not found (404)
NOT_FOUND— chungRECORD_NOT_FOUND— row thiếu cho ID đã cho
Conflict (409)
ALREADY_EXISTS— chung
Validation (400)
INVALID_INPUT— chung; xemdetailsMISSING_REQUIRED— một trường bắt buộc bị thiếuINVALID_FORMAT— giá trị không khớp định dạng mong đợi (ví dụ UUID, URL, email)INVALID_LENGTH— giá trị quá ngắn hoặc quá dàiINVALID_VALUE— giá trị nằm ngoài enum/range cho phépPAYMENT_METHOD_NOT_SUPPORTED— combo provider/asset chưa được enable cho merchantAMOUNT_BELOW_MINIMUM— amount của order dưới sàn theo từng network hoặc theo cấp env.details.floor_usdtrong response mang sàn cấu hình (USD) để bạn có thể surface trực tiếp; textmessagecũng nêu rõ.INSUFFICIENT_BALANCE— ví của người mua không đủ pay asset để cover giao dịch chuyển.INSUFFICIENT_GAS— ví của người mua thiếu phí mạng (gas) bằng đồng native để broadcast giao dịch chuyển.
Payment / billing (402)
INSUFFICIENT_CREDIT— số dư prepaid của merchant không thể bù phí mạng (gas) và phí nền tảng. Top up qua dashboard, rồi retry
Rate limiting (429)
TOO_MANY_REQUESTS— vượt quá rate limit theo IPTOO_MANY_ATTEMPTS— các attempt fail lặp lại trên cùng resource (ví dụ OTP) chạm throttle
Lỗi server (500)
EXTERNAL_SERVICE_ERROR— một provider bên thứ ba bị lỗiINTERNAL_SERVER_ERROR— lỗi bất ngờ; liên hệ support kèm headerDatecủa response và các headerX-RateLimit-*
Availability (503)
SERVICE_UNAVAILABLE— dịch vụ tạm thời không khả dụng; retry với backoff
Lỗi validation (400)
Khi vấn đề là body request bị lỗi định dạng, details là một mảng để
bạn có thể map lỗi về các trường:
{
"code": 400,
"message": "invalid_input",
"details": [
{ "field": "items[0].unit_price", "message": "must be a positive decimal string" },
{ "field": "success_url", "message": "must be a valid https URL" }
]
}Lỗi auth (401)
INVALID_SIGNATURE có ba nguyên nhân thường gặp:
- Sai secret key (kiểm tra lại env var)
- Drift timestamp > 5 phút (đồng bộ NTP)
- Sai chuỗi canonical (thường gặp nhất: quên separator
\n, hoặc ký một body đã được parse rồi stringify lại khác với cái bạn đã gửi)
Xem Xác thực cho thuật toán ký chính xác.
Rate limit (429)
| Surface | Giới hạn |
|---|---|
Mọi route API (kể cả /b2b/v1/*) | Theo địa chỉ IP — 500 req/min, bucket chung. Không theo merchant. |
Public checkout (/checkout/*) | Sub-bucket chặt hơn — 20 req/min theo IP |
X-RateLimit-Limit và X-RateLimit-Remaining được include trên mọi
response bị rate-limit, không chỉ response thành công. Coi chúng là
budget hiện tại cho IP của bạn.
Response bị rate-limit có include header Retry-After (số giây đến
khi cửa sổ reset) — hãy tôn trọng nó. Như một fallback, back off với
jitter — 1s base + exponential lên 30s.
Rate limit có thể thay đổi. Nếu bạn chạm chúng với traffic hợp lệ (ví dụ đối soát một khoảng lịch sử lớn), hãy liên hệ support.
Thiếu credit (402)
INSUFFICIENT_CREDIT (HTTP 402 Payment Required) nghĩa là số dư
prepaid của bạn không thể bù phí mạng (gas) và phí nền tảng cho
thao tác bạn cố thực hiện, thường là settle một thanh toán crypto
hoặc một hành động on-chain được tài trợ phí mạng. Top up từ
merchant dashboard (Billing → Add credit), rồi retry thao tác.
Các thao tác đang chạy sẽ tiếp tục tự động khi số dư được top up.
Lỗi server (5xx)
500 nghĩa là chúng tôi không thể xử lý request. Retry với backoff —
idempotency_key của bạn đảm bảo bạn không charge gấp đôi nếu request
ban đầu đã thành công một phần.
Nếu retry không recover trong một phút, surface một “thanh toán tạm
thời không khả dụng” chung cho người mua và liên hệ support với
endpoint fail, merchant ID của bạn, header Date của response và
các giá trị X-RateLimit-*, và thời gian request xấp xỉ — đó là đủ
cho chúng tôi tìm ra request.
Lỗi delivery webhook
Delivery webhook là một kênh failure riêng — chúng không surface dưới dạng lỗi API vì server của bạn không phải là bên đang gọi. Khi một delivery trả về non-2xx (hoặc timeout), nó được retry với exponential backoff tại 0s, 1min, 5min, 15min, 1h, 6h (tổng sáu lần, cùng schedule như Webhooks → Tổng quan). Lịch sử đầy đủ theo từng delivery hiển thị dưới Developers → Webhooks → [endpoint] → Delivery log trong dashboard. Sau lần thứ sáu, delivery được đánh dấu “Failed” trong dashboard, và bạn có thể replay thủ công khi server của bạn khỏe trở lại.