Sign in
Giới thiệu/Thông báo Webhook

Thông báo Webhook

Nhận cập nhật trạng thái thanh toán và rút tiền theo thời gian thực qua webhook được ký HMAC.

Hệ thống 2328.io gửi webhook đến url_callback của bạn mỗi khi trạng thái thanh toán thay đổi. Đây là cách được khuyến nghị để nhận thông báo về các thanh toán thành công.

Định dạng yêu cầu

  • Method: POST
  • Content-Type: application/json
  • Chữ ký: trường sign trong thân yêu cầu

Payload

Thân webhook giống hệt phản hồi /v1/payment/info, kèm theo một trường sign dùng cho việc xác minh chữ ký.

Thanh toán thành công

JSON
{
  "uuid": "db17d490-15b6-47b9-9015-91d1d8b119f2",
  "order_id": "ORDER-12345",
  "amount": "180.00000000",
  "currency": "RUB",
  "url": "https://go.2328.io/db17d490-15b6-47b9-9015-91d1d8b119f2",
  "expires_at": "2026-05-09T16:56:58+03:00",
  "created_at": "2026-05-09T15:56:58+03:00",
  "payer_currency": "TON",
  "payer_amount": "0.95256917",
  "network": "TON",
  "address": "UQA0RevhkCQx-EltyNgPPeG8dqtnCz7ZslOzMdNQlLxVaNBb",
  "payment_status": "paid",
  "txid": "41c2a327323480af8e705d05deb09c238a41779928832abef4bb77c862357b11",
  "payment_amount": "0.95256917",
  "merchant_amount": "0.949711462490000000",
  "amount_usd": "2.41324380",
  "exchange_rate": "0.01340691",
  "sign": "6f8c15b6e53b506d5bfa38ed3fb3b50697af73434262153c02e412541372f04d"
}

Thanh toán bị hủy / thất bại

Khi thanh toán không ở trạng thái cuối paid, các trường txid, payment_amountmerchant_amountnull:

JSON
{
  "uuid": "48edaf2d-2c49-4638-8f86-88636f661c1f",
  "order_id": "ORDER-12345",
  "amount": "2800.00000000",
  "currency": "RUB",
  "url": "https://go.2328.io/48edaf2d-2c49-4638-8f86-88636f661c1f",
  "expires_at": "2026-05-09T06:19:04+03:00",
  "created_at": "2026-05-09T05:19:04+03:00",
  "payer_currency": "ETH",
  "payer_amount": "0.01620968",
  "network": "ETH-ERC20",
  "address": "0x37c20d6d96d130Bc5B33D832e43b8e16aACe0c59",
  "payment_status": "cancel",
  "txid": null,
  "payment_amount": null,
  "merchant_amount": null,
  "amount_usd": "37.53934800",
  "exchange_rate": "0.01340691",
  "sign": "40ce68ad9691ad54e684329d75ab5adaf5b01409a2d18d3e0110b8c1be605342"
}

Tham chiếu trường

FieldTypeDescription
uuidstringUUID thanh toán
order_idstringID đơn hàng của bạn
amountdecimal (8 dp)Số tiền pháp định theo currency
currencystringĐồng tiền pháp định mà merchant yêu cầu
urlstringURL trang checkout được hosted
expires_atstring (ISO 8601)Thời điểm phiên thanh toán hết hạn
created_atstring (ISO 8601)Thời điểm phiên thanh toán được tạo
payer_currencystringCrypto mà người trả đang dùng để thanh toán
payer_amountdecimal (8 dp)Số crypto dự kiến
networkstringMạng blockchain
addressstringĐịa chỉ nạp tiền
payment_statusstringMột trong: pending, check, paid, underpaid_check, underpaid, overpaid, cancel, aml_lock (xem References)
txidstring | nullHash giao dịch blockchain, chỉ có sau khi thanh toán được xác nhận
payment_amountdecimal | nullSố tiền thực sự đã trả, chỉ có sau khi thanh toán
merchant_amountdecimal (18 dp) | nullSố tiền được ghi có cho merchant sau khi trừ phí
amount_usddecimal (8 dp)Số tiền quy ra USD tại thời điểm tạo
exchange_ratedecimalTỷ giá Crypto / fiat đã sử dụng
signstring (hex)Chữ ký HMAC-SHA256 của payload

Xác minh chữ ký

Để xác minh chữ ký webhook:

  1. Trích xuất trường sign từ payload
  2. Loại bỏ trường sign khỏi object
  3. Mã hóa các trường còn lại thành JSON
  4. Mã hóa Base64 chuỗi JSON
  5. Tính HMAC-SHA256 của chuỗi Base64 bằng API_KEY của bạn
  6. So sánh chữ ký vừa tính với giá trị sign bằng so sánh thời gian không đổi
PHP
<?php
function verifyWebhookSign(array $data, string $apiKey): bool {
    $receivedSign = $data['sign'] ?? '';
    unset($data['sign']);

    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $base64 = base64_encode($json);
    $calculated = hash_hmac('sha256', $base64, $apiKey);

    return hash_equals($calculated, $receivedSign);
}

$apiKey = 'YOUR_API_KEY';
$payload = json_decode(file_get_contents('php://input'), true);

if (!verifyWebhookSign($payload, $apiKey)) {
    http_response_code(401);
    exit;
}

switch ($payload['payment_status']) {
    case 'paid':
    case 'overpaid':
        // Credit the order — check idempotency by order_id first
        break;
    case 'underpaid_check':
    case 'underpaid':
    case 'cancel':
        break;
}

http_response_code(200);

Luôn xác minh chữ ký trước khi ghi có bất kỳ khoản tiền nào cho người dùng. Một webhook không có chữ ký hoặc có chữ ký sai có thể là một yêu cầu giả mạo.

Webhook rút tiền

Khi status của một rút tiền thay đổi, hệ thống gửi webhook POST đến URL url_callback được truyền khi tạo rút tiền. Nếu không cung cấp url_callback, sẽ không có webhook nào được gửi cho rút tiền đó.

Webhook rút tiền phải được xác minh bằng Payout API key — không phải API key thông thường. Thuật toán ký giống hệt webhook thanh toán (loại bỏ sign, mã hóa JSON, base64, HMAC-SHA256), chỉ khác key.

Payload

JSON
{
  "uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
  "order_id": "4dfdcc84402b1185b71cbe399321533e",
  "status": "completed",
  "currency": "TRX",
  "network": "TRX-TRC20",
  "amount": "3.00",
  "merchant_amount": "3.00",
  "network_amount": "3.00",
  "amount_usd": "1.04",
  "to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
  "memo": null,
  "txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
  "block_number": null,
  "error_type": null,
  "created_at": "2026-05-07T00:08:38+03:00",
  "updated_at": "2026-05-07T00:08:54+03:00",
  "from_currency": "USDT",
  "debited_amount": "1.050735",
  "debited_currency": "USDT",
  "sign": "925ad7bf3d6841864101f7cc2c7e30652e70a06cdb04dbe07a0129480000ce4a"
}

Tham chiếu trường

FieldTypeDescription
uuidstringUUID rút tiền
order_idstringID idempotency / tham chiếu của bạn, nếu bạn có cung cấp
statusstringpending, completed, failed, cancelled (xem References)
currencystringĐồng tiền rút
networkstringMạng blockchain
amountdecimalSố tiền rút (theo currency)
merchant_amountdecimalSố tiền bị trừ từ số dư merchant
network_amountdecimalSố tiền thực sự được gửi trên chuỗi
amount_usddecimalGiá trị USD tại thời điểm rút tiền
to_addressstringĐịa chỉ blockchain nhận tiền
memostring | nullMemo / destination tag, nếu có dùng
txidstring | nullHash giao dịch blockchain, được đặt khi completed
block_numberinteger | nullChiều cao block của giao dịch trên chuỗi
error_typestring | nullLý do khi status = failed (ví dụ aml_risk, xem References)
created_atstring (ISO 8601)Thời điểm rút tiền được tạo
updated_atstring (ISO 8601)Thời điểm trạng thái thay đổi gần nhất
from_currencystringSố dư nguồn mà khoản rút tiền đã bị trừ khi sử dụng quy đổi tự động (ví dụ USDT cho khoản rút BTC)
debited_amountdecimalSố tiền bị trừ từ số dư from_currency
debited_currencystringĐồng tiền của khoản trừ
signstring (hex)Chữ ký HMAC-SHA256 của payload, ký bằng Payout API key

Best practices

  • Idempotency — Luôn kiểm tra xem thanh toán đã được xử lý hay chưa (theo order_id hoặc uuid). Webhook có thể đến nhiều lần.
  • Phản hồi nhanh — Trả về HTTP 200 càng nhanh càng tốt. Đẩy các công việc nặng sang một queue chạy nền.
  • Retry — Nếu hệ thống không nhận được HTTP 200, webhook sẽ được gửi lại sau 2 phút. Tối đa 5 lần thử lại.
  • Xử lý bất đồng bộ — Xử lý sự kiện webhook bất đồng bộ để tránh chặn phản hồi.
  • Bảo mật — LUÔN xác minh chữ ký sign trước khi tin tưởng payload.

Webhook có thể đến không theo thứ tự. Đừng cho rằng webhook đầu tiên bạn nhận được là trạng thái cuối cùng — luôn lấy lại qua /v1/payment/info (hoặc /v1/payout/status/{uuid}) nếu cần chắc chắn.