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 tuân theo định dạng phản hồi /v1/payment/info và bổ sung tx_explorer_url cùng trường sign dùng để 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",
  "tx_explorer_url": "https://tonviewer.com/transaction/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,
  "tx_explorer_url": 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
tx_explorer_urlstring | nullURL giao dịch trên trình khám phá blockchain. Là null khi không có txid hoặc giao dịch là chuyển P2P nội bộ.
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",
  "tx_explorer_url": "https://tronscan.org/#/transaction/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
tx_explorer_urlstring | nullURL giao dịch trên trình khám phá blockchain. Là null khi không có txid hoặc giao dịch là chuyển P2P nội bộ.
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.

Hợp đồng giao hàng và xử lý

Sử dụng thứ tự sau bên trong endpoint webhook của bạn:

  1. Đọc nội dung yêu cầu mà không ghi lại các bí mật hoặc chữ ký đầy đủ.
  2. Xác định xem đó là sự kiện thanh toán/ví tĩnh hay sự kiện trả tiền để chọn đúng khóa API.
  3. Xóa sign, tái tạo các byte JSON đã được ghi chép, tính HMAC-SHA256 và so sánh trong thời gian cố định.
  4. Xác thực các định danh, chuỗi số thập phân và giá trị trạng thái cần thiết.
  5. Chèn nguyên tử một bản ghi hộp thư/idempotency. Nếu đã tồn tại, trả về HTTP 200 mà không lặp lại các tác động phụ.
  6. Cam kết thay đổi đơn hàng/sổ cái và đưa vào hàng đợi email, phân tích hoặc thông báo không quan trọng.
  7. Trả về HTTP 200 nhanh chóng.

Không gọi các dịch vụ bên thứ ba chậm trong khi giữ giao dịch idempotency. Một thời gian chờ sau khi bạn cam kết nhưng trước khi trả về có thể gây ra việc thử lại; bản sao phải quan sát khóa hộp thư đã cam kết và trở thành thao tác không.

Sự kiệnDanh tính chínhGhi chú
Phiên thanh toánuuid + bằng chứng trạng thái/phiên bảnCùng một hóa đơn có thể phát ra nhiều thay đổi trạng thái hợp pháp.
Thanh toán bù một phầnhóa đơn uuid + txidCó thể có nhiều chuyển khoản thuộc cùng một hóa đơn chưa thanh toán đầy đủ.
Nạp ví tĩnhmạng + txidorder_id được sử dụng lại cho mỗi lần nạp vào ví đó.
Chi trảchi trả uuid + trạng tháiKhông bao giờ tạo chi trả thứ hai từ logic thử lại webhook.

Nếu sơ đồ của bạn không có id sự kiện, lưu trữ băm payload đã xác minh như bằng chứng kiểm toán bổ sung, nhưng không thay thế các nhận dạng kinh doanh ở trên bằng dấu thời gian.

Sắp xếp và đối chiếu

Giao hàng ít nhất một lần và các thông báo trạng thái có thể xảy ra đồng thời. Thực hiện các quy tắc nghiệp vụ theo thứ tự tăng dần thay vì “yêu cầu cuối cùng thắng”:

  • không bao giờ chuyển một đơn hàng đã hoàn thành trở lại check vì một sự kiện cũ đến muộn;
  • cho phép underpaid_check nhận thêm các txid mà không lặp lại các khoản tín dụng trước đó;
  • xem paidoverpaid như các trạng thái thanh toán thành công, đồng thời giữ nguyên các số tiền khác nhau của chúng;
  • giữ underpaid như kết quả thanh toán một phần cuối cùng trừ khi API có thẩm quyền sau đó báo cáo trạng thái khác;
  • điều hướng aml_lock để xem xét và không để một nhân viên thử lại chung thực hiện nó;
  • truy vấn thông tin thanh toán/chi trả bất cứ khi nào chuyển đổi không khả thi, thiếu bối cảnh, hoặc không rõ ràng về tài chính.

Chạy một cuộc đối chiếu theo lịch ngay cả khi việc gửi webhook có vẻ ổn định. So sánh trạng thái thiết bị đầu cuối cục bộ và số tiền đã ghi có với /v1/payment/info, /v1/static-wallet/transactions, hoặc /v1/payout/status/{uuid} và cảnh báo các khác biệt thay vì âm thầm ghi đè lịch sử sổ cái.

Bảo mật điểm cuối webhook

  • Yêu cầu HTTPS và giữ callback có thể truy cập công khai; các mục tiêu callback riêng/loopback sẽ bị từ chối trong quá trình tạo thanh toán.
  • Thực thi giới hạn kích thước thân yêu cầu nhỏ và kiểu nội dung JSON.
  • Giới hạn tốc độ trước khi thực hiện công việc tốn kém, nhưng để đủ khoảng trống cho các đợt bùng phát hợp lý và thử lại.
  • Không bao giờ cho phép một webhook chỉ dựa trên địa chỉ IP nguồn. Danh sách trắng mạng là một biện pháp phòng thủ nhiều lớp; xác minh HMAC là bắt buộc.
  • Làm mờ sign, khóa API, địa chỉ theo yêu cầu của chính sách, và dữ liệu cá nhân từ nhật ký ứng dụng.
  • Giữ cả khóa hiện tại và khóa thay thế đã được lên lịch sẵn có sẵn trong thời gian luân phiên có kiểm soát; không bao giờ đoán khóa nào đã ký một sự kiện.
  • Trả về một thông báo lỗi chung khi chữ ký không hợp lệ để điểm cuối không trở thành một kênh tra cứu khóa hoặc tài khoản.