Sign in
Pengantar/Notifikasi Webhook

Notifikasi Webhook

Terima pembaruan status pembayaran dan penarikan secara real-time melalui webhook yang ditandatangani HMAC.

Sistem 2328.io mengirim webhook ke url_callback Anda setiap kali status pembayaran berubah. Ini adalah cara yang direkomendasikan untuk mendapatkan notifikasi tentang pembayaran sukses.

Format permintaan

  • Method: POST
  • Content-Type: application/json
  • Tanda tangan: field sign di body permintaan

Payload

Body webhook mengikuti format respons /v1/payment/info dan menambahkan tx_explorer_url serta field sign untuk verifikasi tanda tangan.

Pembayaran sukses

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"
}

Pembayaran dibatalkan / gagal

Saat pembayaran tidak dalam status terminal paid, txid, payment_amount, dan merchant_amount adalah null:

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"
}

Referensi field

FieldTipeDeskripsi
uuidstringUUID pembayaran
order_idstringID pesanan Anda
amountdecimal (8 dp)Jumlah fiat dalam currency
currencystringMata uang fiat yang diminta merchant
urlstringURL checkout terhosting
expires_atstring (ISO 8601)Saat sesi pembayaran kedaluwarsa
created_atstring (ISO 8601)Saat sesi pembayaran dibuat
payer_currencystringKripto yang dibayarkan oleh pembayar
payer_amountdecimal (8 dp)Jumlah kripto yang diharapkan
networkstringJaringan blockchain
addressstringAlamat deposit
payment_statusstringSalah satu dari: pending, check, paid, underpaid_check, underpaid, overpaid, cancel, aml_lock (lihat References)
txidstring | nullHash tx blockchain, hadir hanya setelah pembayaran terkonfirmasi
tx_explorer_urlstring | nullURL transaksi di penjelajah blockchain. Bernilai null jika txid tidak ada atau transfer merupakan P2P internal.
payment_amountdecimal | nullJumlah aktual yang dibayar, hadir hanya setelah pembayaran
merchant_amountdecimal (18 dp) | nullJumlah yang dikreditkan ke merchant setelah biaya
amount_usddecimal (8 dp)Jumlah dalam USD pada saat pembuatan
exchange_ratedecimalNilai tukar Kripto / fiat yang digunakan
signstring (hex)Tanda tangan HMAC-SHA256 dari payload

Memverifikasi tanda tangan

Untuk memverifikasi tanda tangan webhook:

  1. Ekstrak field sign dari payload
  2. Hapus field sign dari objek
  3. Encode field yang tersisa sebagai JSON
  4. Encode JSON tersebut ke Base64
  5. Hitung HMAC-SHA256 dari string Base64 menggunakan API_KEY Anda
  6. Bandingkan tanda tangan yang dihitung dengan nilai sign menggunakan perbandingan constant-time
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);

Selalu verifikasi tanda tangan sebelum mengkreditkan dana apa pun ke pengguna. Webhook yang tidak ditandatangani atau ditandatangani secara tidak benar dapat menjadi permintaan palsu.

Webhook penarikan

Saat status sebuah penarikan berubah, sistem mengirim webhook POST ke URL url_callback yang diteruskan saat penarikan dibuat. Jika url_callback tidak disediakan, tidak ada webhook yang dikirim untuk penarikan tersebut.

Webhook penarikan harus diverifikasi dengan Payout API key Anda — bukan API key biasa. Algoritma penandatanganan identik dengan webhook pembayaran (hapus sign, encode JSON, base64, HMAC-SHA256), hanya key yang berbeda.

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"
}

Referensi field

FieldTipeDeskripsi
uuidstringUUID penarikan
order_idstringID idempotensi / referensi Anda, jika Anda menyediakannya
statusstringpending, completed, failed, cancelled (lihat References)
currencystringMata uang penarikan
networkstringJaringan blockchain
amountdecimalJumlah penarikan (dalam currency)
merchant_amountdecimalJumlah yang dibebankan dari saldo merchant
network_amountdecimalJumlah yang sebenarnya dikirim on-chain
amount_usddecimalNilai USD pada saat penarikan
to_addressstringAlamat blockchain penerima
memostring | nullMemo / destination tag, jika digunakan
txidstring | nullHash transaksi blockchain, ditetapkan saat completed
tx_explorer_urlstring | nullURL transaksi di penjelajah blockchain. Bernilai null jika txid tidak ada atau transfer merupakan P2P internal.
block_numberinteger | nullTinggi blok dari transaksi on-chain
error_typestring | nullAlasan saat status = failed (mis. aml_risk, lihat References)
created_atstring (ISO 8601)Saat penarikan dibuat
updated_atstring (ISO 8601)Saat status terakhir berubah
from_currencystringSaldo sumber yang didebet untuk penarikan ketika konversi otomatis digunakan (mis. USDT untuk penarikan BTC)
debited_amountdecimalJumlah yang didebit dari saldo from_currency
debited_currencystringMata uang dari debit
signstring (hex)Tanda tangan HMAC-SHA256 dari payload, ditandatangani dengan Payout API key

Praktik terbaik

  • Idempotensi — Selalu periksa apakah pembayaran sudah diproses (berdasarkan order_id atau uuid). Webhook bisa tiba beberapa kali.
  • Respon cepat — Kembalikan HTTP 200 secepat mungkin. Pindahkan pekerjaan berat ke antrean latar belakang.
  • Retry — Jika sistem tidak menerima HTTP 200, webhook dikirim ulang setelah 2 menit. Maksimum 5 kali percobaan retry.
  • Pemrosesan async — Tangani event webhook secara asinkron untuk menghindari pemblokiran respon.
  • Keamanan — SELALU verifikasi tanda tangan sign sebelum mempercayai payload.

Webhook bisa tiba dengan urutan yang tidak sesuai. Jangan asumsikan webhook pertama yang Anda terima adalah status final — selalu ambil ulang melalui /v1/payment/info (atau /v1/payout/status/{uuid}) jika Anda perlu kepastian.

Kontrak pengiriman dan pemrosesan

Gunakan urutan berikut di dalam endpoint webhook Anda:

  1. Baca isi permintaan tanpa mencatat rahasia atau tanda tangan lengkap.
  2. Identifikasi apakah itu adalah event pembayaran/dompet statis atau event pembayaran keluar sehingga Anda memilih API key yang benar.
  3. Hapus sign, reproduksi byte JSON yang didokumentasikan, hitung HMAC-SHA256, dan bandingkan dalam waktu konstan.
  4. Validasi pengenal yang diperlukan, string desimal, dan nilai status.
  5. Masukkan catatan inbox/idempotensi secara atomik. Jika sudah ada, kembalikan HTTP 200 tanpa mengulangi efek samping.
  6. Komit mutasi pesanan/buku besar dan antrekan email, analitik, atau notifikasi yang tidak kritis.
  7. Kembalikan HTTP 200 dengan cepat.

Jangan memanggil layanan pihak ketiga yang lambat saat memegang transaksi idempoten. Timeout setelah Anda melakukan komit tetapi sebelum mengembalikan dapat menyebabkan percobaan ulang; duplikat harus memperhatikan kunci inbox yang telah dikomit dan menjadi tanpa operasi.

AcaraIdentitas utamaCatatan
Sesi pembayaranuuid + bukti status/versiFaktur yang sama dapat menghasilkan beberapa perubahan status yang sah.
Isi ulang pembayaran sebagianfaktur uuid + txidLebih dari satu transfer bisa dimiliki oleh faktur yang sama yang kurang dibayar.
Deposit dompet statisjaringan + txidorder_id digunakan kembali oleh setiap deposit ke dompet itu.
Pembayaranpembayaran uuid + statusJangan pernah membuat pembayaran kedua dari logika percobaan ulang webhook.

Jika skema Anda tidak memiliki id acara, simpan hash payload yang terverifikasi sebagai bukti audit tambahan, tetapi jangan mengganti identitas bisnis di atas dengan cap waktu.

Pemesan dan rekonsiliasi

Pengiriman setidaknya-satu-kali dan pesan status dapat bersaing. Terapkan aturan bisnis monotonic daripada “permintaan terakhir menang”:

  • jangan pernah memindahkan pesanan yang telah dipenuhi kembali ke check karena event yang lebih lama tiba terlambat;
  • izinkan underpaid_check menerima txid tambahan tanpa mengulang kredit sebelumnya;
  • anggap paid dan overpaid sebagai status penyelesaian yang berhasil, sambil mempertahankan jumlah yang berbeda;
  • tetap pertahankan underpaid sebagai hasil pembayaran parsial final kecuali API yang berwenang kemudian melaporkan status lain;
  • arahkan aml_lock ke peninjauan dan jangan biarkan pekerja ulang generik memenuhinya;
  • meminta info pembayaran/pencairan kapan pun transisi tidak mungkin, konteks hilang, atau secara finansial ambigu.

Jalankan rekonsiliasi terjadwal bahkan ketika pengiriman webhook tampak sehat. Bandingkan status terminal lokal dan jumlah yang dikreditkan dengan /v1/payment/info, /v1/static-wallet/transactions, atau /v1/payout/status/{uuid} dan beri peringatan pada perbedaan alih-alih menimpa riwayat pembukuan secara diam-diam.

Keamanan endpoint webhook

  • Mewajibkan HTTPS dan menjaga callback tetap bisa diakses publik; target callback privat/loopback ditolak saat pembuatan pembayaran.
  • Terapkan batas kecil pada body permintaan dan tipe konten JSON.
  • Batasi kecepatan sebelum pekerjaan yang mahal, tetapi berikan ruang cukup untuk lonjakan sah dan percobaan ulang.
  • Jangan pernah mengizinkan webhook hanya berdasarkan IP sumber. Daftar izin jaringan adalah pertahanan berlapis; verifikasi HMAC wajib.
  • Sunting sign, kunci API, alamat bila diperlukan oleh kebijakan, dan metadata pribadi dari log aplikasi.
  • Simpan baik kunci saat ini maupun kunci pengganti yang dijadwalkan secara eksplisit selama jendela rotasi terkontrol; jangan pernah menebak kunci mana yang menandatangani sebuah kejadian.
  • Kembalikan isi kesalahan umum pada tanda tangan yang tidak valid agar endpoint tidak menjadi oracle kunci atau akun.