Callback

PostmanTest on Postman

Parameter dan struktur notifikasi callback.

Callback

Ketika pembeli berhasil melakukan pembayaran, iPaymu akan mengirimkan notifikasi callback ke endpoint yang telah Anda konfigurasi. Dokumen ini menjelaskan struktur lengkap callback, header yang diperlukan, dan proses validasi signature.

Konfigurasi

Anda dapat mengatur tipe konten callback di dashboard iPaymu. Pilih format yang sesuai dengan sistem Anda:

Content-TypeDeskripsi
application/x-www-form-urlencodedDefault. Direkomendasikan untuk stabilitas dan kompatibilitas yang lebih baik
application/jsonFormat alternatif jika sistem Anda lebih menyukai JSON

Link Pengaturan:

Secret Key untuk Validasi Signature

Untuk melakukan validasi signature pada callback, gunakan Nomor VA (Virtual Account) akun iPaymu Anda sebagai secret key.

Cara mendapatkan Secret Key:

  1. Login ke Dashboard iPaymu
  2. Pilih menu Integration atau Pengaturan
  3. Klik API Key atau Kunci API
  4. Nomor VA Anda akan terlihat di bagian API Key atau Merchant VA

Contoh Secret Key: 1179001234567890

⚠️ Penting: Jangan pernah membagikan Nomor VA Anda atau menyimpannya di repositori publik. Gunakan environment variables untuk menyimpan secret key di production.


Alur Proses Callback

Berikut diagram alur lengkap proses callback dari iPaymu:

Diagram generated via mermaid

Catatan Penting:

  • Jika signature tidak valid, jangan proses transaksi
  • Selalu response dengan HTTP 200 untuk menghentikan retry
  • Implementasikan idempotency untuk menghindari duplikasi

Catatan Penting

Sebelum mengimplementasikan penanganan callback, pastikan Anda memahami poin-poin berikut:

TopikDeskripsi
Mekanisme RetryiPaymu akan secara otomatis melakukan retry callback jika server Anda tidak merespons dengan 200 OK. Siapkan penanganan idempotensi yang tepat untuk menghindari duplikasi pemrosesan.
IdempotensiKarena retry dapat terjadi, selalu periksa status transaksi di database sebelum memproses. Ini mencegah double-posting ketika callback yang sama datang berkali-kali.
Validasi SignatureSelalu validasi header X-Signature untuk memastikan request benar-benar berasal dari iPaymu. Jangan pernah memproses callback tanpa verifikasi.
Secret KeyGunakan Nomor VA Merchant Anda sebagai secret key untuk validasi signature.

Headers

Setiap request callback menyertakan headers berikut. X-Signature sangat penting untuk verifikasi keamanan.

HeaderValueDeskripsi
Acceptapplication/jsonTipe konten yang diharapkan iPaymu untuk response
Content-Typeapplication/jsonFormat body request
X-External-ID20251111101052209120Identifier unik untuk request callback ini
X-Signaturec9594cc8...Signature HMAC-SHA256 untuk validasi
X-Timestamp2025-11-11T10:10:52+07:00Timestamp ketika callback dibuat

Contoh Payload Lengkap

Berikut adalah contoh payload lengkap yang dikirimkan oleh iPaymu:

Contoh x-www-form-urlencoded

POST /callback HTTP/1.1
Host: your-domain.com
Content-Type: application/x-www-form-urlencoded
X-Signature: c9594cc8...
X-Timestamp: 2025-11-11T10:10:52+07:00

buyer_email=customer%40example.com&
buyer_name=John%20Doe&
buyer_phone=081234567890&
created_at=2025-11-11%2010%3A00%3A00&
expired_at=2025-11-11%2011%3A00%3A00&
fee=1500&
paid_at=2025-11-11%2010%3A10%3A52&
reference_id=ORDER123456&
sid=SESSION789&
status=berhasil&
status_code=1&
status_desc=Success&
channel=bca&
paid_off=98500&
product=Product%20Name&
quantity=1&
merchant=1179001234567890&
merchant_name=Your%20Store&
system_notes=Payment%20completed&
trscode=TRX789012&
trx_id=12345678&
unique_code=0&
via=va&
payment_no=1234567890&
va=1234567890&
url=https%3A%2F%2Fyour-domain.com%2Fcallback&
additional_info=%5B%5D&
transaction_status_code=1&
settlement_status=unsettle&
settlement_date=null&
expired_time=3600&
expired_unix=1762842000&
is_escrow=0&
is_refund=0&
is_sandbox=false&
total=100000&
amount=100000&
sub_total=100000&
referenceId=ORDER123456

Contoh application/json

{
  "buyer_email": "customer@example.com",
  "buyer_name": "John Doe",
  "buyer_phone": "081234567890",
  "created_at": "2025-11-11 10:00:00",
  "expired_at": "2025-11-11 11:00:00",
  "fee": "1500",
  "paid_at": "2025-11-11 10:10:52",
  "reference_id": "ORDER123456",
  "sid": "SESSION789",
  "status": "berhasil",
  "status_code": "1",
  "status_desc": "Success",
  "channel": "bca",
  "paid_off": "98500",
  "product": "Product Name",
  "quantity": "1",
  "merchant": "1179001234567890",
  "merchant_name": "Your Store",
  "system_notes": "Payment completed",
  "trscode": "TRX789012",
  "trx_id": "12345678",
  "unique_code": "0",
  "via": "va",
  "payment_no": "1234567890",
  "va": "1234567890",
  "url": "https://your-domain.com/callback",
  "additional_info": [],
  "transaction_status_code": "1",
  "settlement_status": "unsettle",
  "settlement_date": null,
  "expired_time": "3600",
  "expired_unix": "1762842000",
  "is_escrow": "0",
  "is_refund": "0",
  "is_sandbox": "false",
  "total": "100000",
  "amount": "100000",
  "sub_total": "100000",
  "referenceId": "ORDER123456"
}

Struktur Data Callback

Tabel berikut menunjukkan semua kemungkinan field dalam response callback. Perhatikan dengan seksama tipe data — menggunakan tipe yang salah saat validasi signature akan menyebabkan ketidakcocokan hash.

Field Transaksi Inti

FieldTipeDeskripsiCatatan Validasi
trx_idIntegerID transaksi unik di sistem iPaymuUbah dari String jika menerima via Form Data
sidStringSession ID transaksi-
reference_idStringReference ID merchant Anda-
statusStringStatus teks: berhasil, pending, expired-
status_codeIntegerKode status: 1 (Sukses), 0 (Pending), -2 (Expired)Ubah dari String "1" ke Integer 1

Field Jumlah

FieldTipeDeskripsiCatatan Validasi
sub_totalStringJumlah bersih transaksiBiarkan sebagai String
totalStringJumlah bruto transaksiBiarkan sebagai String
amountStringJumlah total transaksiBiarkan sebagai String
feeStringBiaya adminBiarkan sebagai String
paid_offIntegerJumlah bersih diterima (amount - fee)Ubah dari String ke Integer

Field Timestamp

FieldTipeDeskripsiCatatan Validasi
created_atStringWaktu pembuatan transaksiFormat: YYYY-MM-DD HH:MM:SS
expired_atStringWaktu kedaluwarsa transaksi-
paid_atStringWaktu pembayaran selesai-
settlement_statusStringStatus settlementsettled atau unsettle

Field Status

FieldTipeDeskripsiCatatan Validasi
transaction_status_codeIntegerKode status asliUbah dari String ke Integer
is_escrowBooleanIndikator escrowUbah String "0" ke false, "1" ke true

Detail Pembayaran

FieldTipeDeskripsiCatatan Validasi
viaStringMetode pembayaran (contoh: va)-
channelStringChannel pembayaran (contoh: bca, mandiri)-
payment_noStringNomor VA atau pembayaranPenting: Jaga sebagai String agar angka 0 di depan tidak hilang
vaStringNomor VA MerchantSama seperti payment_no. Hanya ada untuk transaksi VA
system_notesStringCatatan yang dihasilkan sistem-

Informasi Pembeli

FieldTipeDeskripsiCatatan Validasi
buyer_nameStringNama pembeli-
buyer_emailStringEmail pembeli-
buyer_phoneStringNomor HP pembeli-

Field Tambahan

FieldTipeDeskripsiCatatan Validasi
additional_infoArrayInformasi tambahanDiperlukan: Sertakan [] bahkan jika field tidak dikirim
urlStringURL Callback-

Field Bersyarat

Beberapa field memiliki perilaku yang berbeda tergantung pada metode pembayaran. Berikut yang perlu diperhatikan:

FieldPerilaku
transaction_status_code1 = Pembayaran langsung settle. 6 = Pembayaran diterima (settlement dapat diproses secara terpisah)
payment_noTerisi untuk transaksi VA. Kosong untuk pembayaran QRIS dan e-wallet
vaHanya ada untuk transaksi VA. Tidak disertakan untuk pembayaran QRIS dan e-wallet

Validasi Signature

Untuk memastikan callback benar-benar berasal dari iPaymu, Anda harus memvalidasi signature pada setiap request. Berikut langkah-langkah prosesnya:

Langkah-langkah Validasi

  1. Ambil Data — Dapatkan semua data dari req.body
  2. Normalisasi Tipe — Ubah nilai ke tipe data yang benar
    • String ke Integer: trx_id, status_code, transaction_status_code, paid_off
    • String ke Boolean: is_escrow
    • String [] ke Array []: additional_info
  3. Tangani Field yang Hilang — Jika additional_info tidak ada (umum terjadi dengan x-www-form-urlencoded), tambahkan manual sebagai array kosong
  4. Urutkan Key — Urutkan semua key secara Ascending (A-Z) dengan sensitivitas huruf besar/kecil
  5. Konversi ke JSON — Serialisasi object yang sudah diurutkan ke string JSON
  6. URL Escape (Opsional) — Escape garis miring (/\/) jika mengikuti standar ketat json_encode PHP
  7. Generate Hash — Buat HMAC-SHA256 menggunakan string JSON dan secret key (nomor VA) Anda
  8. Bandingkan — Cocokkan hash yang Anda buat dengan header X-Signature

Contoh Kode Implementasi

import crypto from 'crypto';
import express from 'express';

const app = express();

app.use(express.urlencoded({ extended: true }));

function normalizeData(rawData) {
    const result = {};
    for (const key in rawData) {
        let val = rawData[key];
        if (key === 'is_escrow') {
            result[key] = (val === 'true' || val === '1' || val === 1);
        }
        else if (['trx_id', 'status_code', 'transaction_status_code', 'paid_off'].includes(key)) {
            result[key] = parseInt(val, 10);
        }
        else if (key === 'additional_info') {
            if (val === '[]') result[key] = [];
            else result[key] = val;
        }
        else {
            result[key] = String(val);
        }
    }
    if (!result.hasOwnProperty('additional_info')) {
        result['additional_info'] = [];
    }
    return result;
}

function phpKsort(obj) {
    return Object.keys(obj)
        .sort((a, b) => a.localeCompare(b))
        .reduce((sortedObj, key) => {
            sortedObj[key] = obj[key];
            return sortedObj;
        }, {});
}

app.post('/callback', (req, res) => {
    const secretKey = '123456'; // Nomor VA akun iPaymu Anda (lihat di Dashboard > Integration > API Key)
    const receivedSignature = req.headers['x-signature'];

    // Step 1 & 2: Ambil dan normalisasi data
    const normalizedData = normalizeData(req.body);
    if (normalizedData.signature) delete normalizedData.signature;

    // Step 3 & 4: Sort key
    const sortedData = phpKsort(normalizedData);
    
    // Step 5 & 6: Konversi ke JSON dengan escape
    let jsonBody = JSON.stringify(sortedData);
    jsonBody = jsonBody.replace(/\//g, '\\/');

    // Step 7: Generate hash
    const calculatedSignature = crypto
        .createHmac('sha256', secretKey)
        .update(jsonBody)
        .digest('hex');

    // Step 8: Bandingkan signature
    if (calculatedSignature === receivedSignature) {
        console.log('✅ Signature valid');
        
        // TODO: Update status transaksi di database
        // TODO: Pastikan idempotency - cek apakah sudah diproses sebelumnya
        
        res.status(200).json({ status: 'OK' });
    } else {
        console.log('❌ Signature tidak valid');
        console.log('Expected:', receivedSignature);
        console.log('Got:     ', calculatedSignature);
        res.status(400).send('Invalid Signature');
    }
});

app.listen(PORT, () => {
  console.log('═══════════════════════════════════════════════════');
  console.log('  iPaymu Callback Handler - URL-encoded');
  console.log('═══════════════════════════════════════════════════');
  console.log(`  Server running on http://localhost:${PORT}`);
  console.log(`  Endpoint: POST http://localhost:${PORT}/callback`);
  console.log('═══════════════════════════════════════════════════');
});

Panduan Debugging

Jika signature validation selalu gagal, ikuti langkah-langkah debugging berikut:

1. Log Semua Data

// Tambahkan logging detail di callback handler
app.post('/callback', (req, res) => {
    console.log('=== CALLBACK DEBUG ===');
    console.log('Headers:', JSON.stringify(req.headers, null, 2));
    console.log('Body (raw):', req.body);
    console.log('Content-Type:', req.headers['content-type']);
    console.log('X-Signature:', req.headers['x-signature']);
    
    // Lanjutkan validasi...
});

2. Periksa JSON Output

// Print JSON yang digunakan untuk signature generation
const sortedData = phpKsort(normalizedData);
let jsonBody = JSON.stringify(sortedData);
jsonBody = jsonBody.replace(/\//g, '\\/');

console.log('JSON untuk signature:');
console.log(jsonBody);
// Output harus persis sama dengan yang diharapkan iPaymu

3. Bandingkan Byte per Byte

// Jika signature tidak cocok, bandingkan karakter per karakter
function compareSignatures(sig1, sig2) {
    console.log('Signature comparison:');
    console.log('Expected:', sig1);
    console.log('Got:     ', sig2);
    
    for (let i = 0; i < Math.max(sig1.length, sig2.length); i++) {
        const c1 = sig1[i] || 'N/A';
        const c2 = sig2[i] || 'N/A';
        if (c1 !== c2) {
            console.log(`Mismatch at index ${i}: '${c1}' vs '${c2}'`);
        }
    }
}

Kesalahan Umum

Berikut adalah kesalahan yang sering terjadi saat implementasi callback:

1. Tidak Mengkonversi Tipe Data

Salah:

const data = req.body; // Langsung pakai tanpa normalisasi

Benar:

const normalizedData = normalizeData(req.body);
// Pastikan trx_id, status_code, dll dalam tipe Integer

2. Key Sorting Tidak Sesuai

Salah:

const sorted = Object.keys(data).sort(); // Mungkin case-insensitive

Benar:

const sorted = Object.keys(data).sort((a, b) => a.localeCompare(b));

3. Tidak Escape Slash di JSON

Salah:

const jsonBody = JSON.stringify(sortedData); // URL tetap http://...

Benar:

let jsonBody = JSON.stringify(sortedData);
jsonBody = jsonBody.replace(/\//g, '\\/'); // URL jadi http:\/\/...

4. Menghapam additional_info

Salah:

// Tidak menambahkan additional_info jika tidak ada di request

Benar:

if (!result.hasOwnProperty('additional_info')) {
    result['additional_info'] = [];
}

5. Menggunakan Secret Key yang Salah

Salah:

const secretKey = 'api_key_atau_lainnya'; // Bukan VA Number

Benar:

const secretKey = '123456'; // Nomor VA akun iPaymu Anda (lihat di Dashboard > Integration > API Key)

Checklist Implementasi

Sebelum go-live, pastikan Anda telah:

  • Mengkonfigurasi callback URL di dashboard iPaymu
  • Menggunakan VA Number sebagai secret key
  • Mengimplementasikan normalisasi tipe data dengan benar
  • Mengurutkan key secara ascending (A-Z)
  • Meng-escape garis miring dalam JSON
  • Merespons dengan HTTP 200 untuk request valid
  • Mengimplementasikan idempotency (cek duplikat)
  • Meng-log semua callback untuk debugging
  • Menguji dengan data dummy dari iPaymu
  • Memiliki fallback mechanism jika callback gagal

Sumber: Dokumentasi Callback iPaymu

On this page