UnitPay
  • Products
  • Pricing
  • Docs
  • Help
  • About
  • Security
  • Legal
|
Demo Get a quote

Developer documentation

Callback handler

Last updated: 07 May 2026

Contents

    The callback handler is a URL on your server that UnitPay calls when a payment changes state. It is the source of truth for whether a payment succeeded — never deliver goods or credit a balance based on the customer's browser redirect alone. Configure the URL in the merchant cabinet under Settings → Project → Payment handler URL.

    Endpoint requirements

    • HTTPS only. Plain HTTP is rejected by the cabinet validator.
    • A registered domain name; raw IP addresses and embedded credentials are not accepted.
    • The handler domain does not need to match your website domain. https://api.example.com/unitpay/callback is fine.
    • Respond within 10 seconds. Long-running work (fulfilment, emails) belongs in a background job, not in the response path.

    Methods you must handle

    methodWhen it is sentYour job
    checkBefore the customer is asked for payment details.Verify the order exists, the amount and currency match, and the customer is allowed to pay. Do not deliver yet.
    payAfter UnitPay confirms a successful charge.Mark the order as paid in your system; deliver the goods or credit the balance.
    preauthWhen funds are authorised but not captured yet.Reserve inventory if relevant; do not deliver until pay arrives.
    errorAn error happened on the gateway side. May be followed by a successful pay.Log it; do not treat the order as failed unless no pay arrives within the payment window.

    check is disabled by default for new projects. Enable it from the cabinet only after your handler is verified to handle it correctly — rejecting check rejects the payment.

    Request shape

    UnitPay sends a GET request. Parameters arrive as query-string fields (or form-encoded body). Common fields:

    ParameterDescription
    methodOne of check, pay, preauth, error.
    params[unitpayId]UnitPay's unique payment identifier. Persist it on first sight; use it as the idempotency key.
    params[projectId]Your project ID. Reject any callback for an unexpected project ID.
    params[account]The customer or order identifier you passed to initPayment.
    params[orderSum]Amount of the original order in major currency units. Compare against your stored order.
    params[orderCurrency]ISO 4217 currency code of the original order.
    params[payerSum]Amount actually charged to the customer (after FX, fees).
    params[payerCurrency]Currency the customer was charged in.
    params[date]Payment timestamp in YYYY-MM-DD HH:MM:SS WIB.
    params[test]1 for test-mode, 0 for live payments.
    params[signature]Request signature. Verify before trusting any other parameter.

    Signature verification

    Verify the signature first. Reject the request with HTTP 200 and an error message if it does not match — do not log the secret key.

    1. Take all keys under params except sign and signature.
    2. Sort those keys alphabetically.
    3. Build a string by concatenating, in order: method, then each sorted parameter value, then your project secret key. Use the literal four-character delimiter {up} between every part.
    4. Hash the string with SHA-256 and lowercase the hex digest.
    5. Compare against params[signature] using a constant-time comparison.

    Reference implementation in Node.js:

    import { createHash } from "node:crypto";
    import { timingSafeEqual } from "node:crypto";
    
    function verifyUnitpaySignature(method, params, secretKey) {
      const sortedKeys = Object.keys(params)
        .filter((k) => k !== "sign" && k !== "signature")
        .sort();
      const sortedValues = sortedKeys.map((k) => String(params[k]));
      const payload = [method, ...sortedValues, secretKey].join("{up}");
      const expected = createHash("sha256").update(payload).digest("hex");
      const received = String(params.signature || "");
      if (expected.length !== received.length) return false;
      return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
    }

    Expected response

    Always reply with HTTP 200 and a JSON body. The shape tells UnitPay whether to advance the payment.

    OutcomeBody
    Accept the request — payment may proceed (or has been processed).{"result": {"message": "Request processed successfully."}}
    Reject the request — payment must not proceed.{"error": {"message": "Order not found."}}

    The error.message string is shown to the customer on the payment form, so write it as customer-facing copy. Do not include internal IDs, stack traces, or PII.

    Idempotency

    UnitPay may retry a callback if the network drops or your handler returns 5xx. Treat params[unitpayId] as the idempotency key:

    • If the same unitpayId arrives twice with the same method, return the response you returned the first time. Do not deliver again, do not credit again.
    • If the customer retries a failed payment, UnitPay issues a new unitpayId. The previous unitpayId stays in your system as failed.

    Funds are credited to your project balance based on UnitPay's record, not on the response your handler returned. If a delivery fails on your side after pay succeeded, retry from your background job — you do not need UnitPay to resend the callback.

    What not to do

    • Do not deliver during preauth — funds are not yet captured.
    • Do not whitelist by IP only. UnitPay's outbound IPs may change; signature verification is the binding check.
    • Do not log the secret key, the full callback URL with secrets in query strings, or the raw signature next to the verification result.
    • Do not return HTTP 200 with an error body for transient failures (database down, queue full). Return 5xx so UnitPay retries.
    • Do not skip check validation when it is enabled — an accepted check commits you to honour the eventual pay.

    Operational notes

    • If no handler URL is configured, callbacks are not sent and payments are auto-approved without your validation. Always set the URL before going live.
    • Failed pay callbacks (handler returned an error) leave the payment in Not completed status. Resolve the underlying issue, then retry from Statistics → Payment details in the cabinet.

    Next steps

    • Create a payment via API — initPayment reference.
    • Generate a payment link — cabinet and programmatic options.
    • Refund a payment — reverse a charge after pay succeeds.

    Callback handler adalah URL pada server Anda yang dipanggil UnitPay ketika status pembayaran berubah. Ini adalah sumber kebenaran untuk menentukan apakah pembayaran berhasil — jangan pernah mengirimkan barang atau menambah saldo hanya berdasarkan redirect browser pelanggan. Atur URL pada kabinet merchant di Pengaturan → Proyek → URL callback handler.

    Persyaratan endpoint

    • Hanya HTTPS. HTTP biasa ditolak oleh validator kabinet.
    • Nama domain terdaftar; alamat IP mentah dan kredensial tertanam tidak diterima.
    • Domain handler tidak harus sama dengan domain situs web Anda. https://api.example.com/unitpay/callback diperbolehkan.
    • Balas dalam 10 detik. Pekerjaan berdurasi panjang (fulfilment, email) sebaiknya dijalankan di background job, bukan pada jalur respons.

    Metode yang harus Anda tangani

    methodKapan dikirimTugas Anda
    checkSebelum pelanggan diminta detail pembayaran.Verifikasi pesanan ada, jumlah dan mata uang cocok, dan pelanggan diizinkan membayar. Jangan kirim barang dulu.
    paySetelah UnitPay mengonfirmasi penagihan berhasil.Tandai pesanan sebagai lunas di sistem Anda; kirim barang atau tambahkan saldo.
    preauthSaat dana diotorisasi tetapi belum dikapitalisasi.Cadangkan stok jika relevan; jangan kirim barang sampai pay tiba.
    errorTerjadi error di sisi gateway. Bisa diikuti oleh pay yang sukses.Catat dalam log; jangan tandai pesanan sebagai gagal kecuali pay tidak datang dalam jendela pembayaran.

    check dinonaktifkan secara default untuk proyek baru. Aktifkan dari kabinet hanya setelah handler Anda terverifikasi menanganinya dengan benar — menolak check berarti menolak pembayaran.

    Bentuk permintaan

    UnitPay mengirim permintaan GET. Parameter datang sebagai field query string (atau body form-encoded). Field umum:

    ParameterDeskripsi
    methodSalah satu dari check, pay, preauth, error.
    params[unitpayId]Identifikasi unik pembayaran di UnitPay. Simpan saat pertama kali muncul; gunakan sebagai kunci idempotensi.
    params[projectId]ID proyek Anda. Tolak callback apa pun untuk ID proyek yang tidak diharapkan.
    params[account]Identifikasi pelanggan atau pesanan yang Anda kirim ke initPayment.
    params[orderSum]Jumlah pesanan asli dalam satuan mata uang utama. Bandingkan dengan pesanan yang Anda simpan.
    params[orderCurrency]Kode mata uang ISO 4217 dari pesanan asli.
    params[payerSum]Jumlah yang sebenarnya ditagihkan kepada pelanggan (setelah konversi mata uang dan biaya).
    params[payerCurrency]Mata uang yang ditagihkan kepada pelanggan.
    params[date]Stempel waktu pembayaran dalam format YYYY-MM-DD HH:MM:SS WIB.
    params[test]1 untuk mode uji, 0 untuk pembayaran live.
    params[signature]Tanda tangan permintaan. Verifikasi sebelum mempercayai parameter lain.

    Verifikasi tanda tangan

    Verifikasi tanda tangan terlebih dahulu. Tolak permintaan dengan HTTP 200 dan body error jika tidak cocok — jangan mencatat kunci rahasia.

    1. Ambil semua kunci di bawah params kecuali sign dan signature.
    2. Urutkan kunci tersebut secara alfabet.
    3. Bangun string dengan menggabungkan, secara berurutan: method, lalu setiap nilai parameter terurut, lalu kunci rahasia proyek Anda. Gunakan pemisah literal empat karakter {up} di antara setiap bagian.
    4. Hash string dengan SHA-256 dan ubah hex digest menjadi huruf kecil.
    5. Bandingkan dengan params[signature] menggunakan perbandingan waktu-konstan.

    Implementasi referensi dalam Node.js:

    import { createHash } from "node:crypto";
    import { timingSafeEqual } from "node:crypto";
    
    function verifyUnitpaySignature(method, params, secretKey) {
      const sortedKeys = Object.keys(params)
        .filter((k) => k !== "sign" && k !== "signature")
        .sort();
      const sortedValues = sortedKeys.map((k) => String(params[k]));
      const payload = [method, ...sortedValues, secretKey].join("{up}");
      const expected = createHash("sha256").update(payload).digest("hex");
      const received = String(params.signature || "");
      if (expected.length !== received.length) return false;
      return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
    }

    Respons yang diharapkan

    Selalu balas dengan HTTP 200 dan body JSON. Bentuknya memberi tahu UnitPay apakah pembayaran dapat dilanjutkan.

    HasilBody
    Terima permintaan — pembayaran dapat dilanjutkan (atau telah diproses).{"result": {"message": "Request processed successfully."}}
    Tolak permintaan — pembayaran tidak boleh dilanjutkan.{"error": {"message": "Order not found."}}

    String error.message ditampilkan kepada pelanggan pada formulir pembayaran, jadi tulis sebagai teks yang ramah pelanggan. Jangan menyertakan ID internal, jejak stack, atau PII.

    Idempotensi

    UnitPay dapat mengulang callback jika jaringan terputus atau handler Anda mengembalikan 5xx. Perlakukan params[unitpayId] sebagai kunci idempotensi:

    • Jika unitpayId yang sama datang dua kali dengan method yang sama, kembalikan respons yang Anda kirim pertama kali. Jangan mengirim ulang barang, jangan menambah saldo lagi.
    • Jika pelanggan mencoba ulang pembayaran yang gagal, UnitPay menerbitkan unitpayId baru. unitpayId sebelumnya tetap di sistem Anda sebagai gagal.

    Dana dikreditkan ke saldo proyek Anda berdasarkan catatan UnitPay, bukan berdasarkan respons handler Anda. Jika pengiriman barang gagal di sisi Anda setelah pay berhasil, ulangi dari background job — Anda tidak perlu meminta UnitPay mengirim ulang callback.

    Yang tidak boleh dilakukan

    • Jangan kirim barang saat preauth — dana belum dikapitalisasi.
    • Jangan whitelist hanya berdasarkan IP. IP keluar UnitPay dapat berubah; verifikasi tanda tangan adalah pemeriksaan yang mengikat.
    • Jangan mencatat kunci rahasia, URL callback lengkap dengan rahasia di query string, atau tanda tangan mentah di sebelah hasil verifikasi.
    • Jangan mengembalikan HTTP 200 dengan body error untuk kegagalan sementara (database mati, antrian penuh). Kembalikan 5xx agar UnitPay mencoba ulang.
    • Jangan melewati validasi check saat diaktifkan — menerima check berarti Anda berjanji menghormati pay yang akan datang.

    Catatan operasional

    • Jika URL handler tidak dikonfigurasi, callback tidak dikirim dan pembayaran disetujui otomatis tanpa validasi Anda. Selalu atur URL sebelum go-live.
    • Callback pay yang gagal (handler mengembalikan error) meninggalkan pembayaran dalam status Not completed. Selesaikan masalah penyebabnya, lalu coba ulang dari Statistik → Detail pembayaran di kabinet.

    Langkah selanjutnya

    • Membuat pembayaran melalui API — referensi initPayment.
    • Membuat tautan pembayaran — opsi kabinet dan programatik.
    • Pengembalian dana pembayaran — balik transaksi setelah pay berhasil.

    Related documentation

    • Create a payment via API Membuat pembayaran melalui API
    • Generate a payment link Membuat tautan pembayaran
    • Refund a payment Pengembalian dana pembayaran

    Company

    • About
    • Security
    • Contact

    Products

    • Online Payments
    • Payouts (USDT)
    • Pricing
    • Docs
    • Help

    Support

    • support@unitpay.net
    • sales@unitpay.net
    • compliance@unitpay.net
    • legal@unitpay.net

    WhatsApp

    +6285121084571

    Mon-Fri 09:00-18:00 WIB

    Legal

    • Privacy Policy
    • Cookie Policy
    • Terms of Service
    • Acceptable Use Policy
    • AML/CFT Statement
    • Legal Center

    Tidak puas? / Not satisfied?

    • Hubungi OJK / Contact OJK: 157 (24 jam) / (24h)
    • LAPS SJK: https://lapssjk.id
    • Email keluhan / Complaint email: complaints@unitpay.net
    • Didit
    • Didit
    • Amazon Web Services (Asia Pacific - Jakarta)
    • Iubenda
    |
    Sitemap

    (c) 2026 PT UNIT GLOBAL SYSTEM. All rights reserved. NPWP: 22.709.627.8-021.000 · NIB: 2511240128903