Webhook Guide

Cara menerima notifikasi otomatis dari Bukasir saat status transaksi berubah.

Cara Kerja Webhook

Ketika status transaksi berubah, Midtrans mengirimkan notifikasi ke Bukasir. Bukasir memverifikasi signature (SHA512) dari Midtrans, lalu memperbarui status transaksi di database. Setelah itu, Bukasir me-relay notifikasi asli dari Midtrans ke webhook URL yang sudah Anda daftarkan.

Alur lengkap:

  1. Midtrans mengirim POST notification ke Bukasir (/api/webhook)
  2. Bukasir memverifikasi signature Midtrans (SHA512)
  3. Bukasir memperbarui status transaksi di database (mapping status Midtrans ke status platform)
  4. Bukasir me-relay notifikasi asli Midtrans ke webhook_url merchant Anda
  5. Field signature_key dihapus dari payload, selain itu dikirim apa adanya
Catatan Payload yang Anda terima adalah notifikasi asli dari Midtrans, bukan format khusus Bukasir. Field-field yang ada mengikuti format standar Midtrans (kecuali signature_key yang dihapus).

Setup Webhook

  1. Login ke dashboard Bukasir
  2. Buka Pengaturan > Profil
  3. Isi field Webhook URL (contoh: https://yourdomain.com/webhook)
  4. Klik Simpan
Peringatan Webhook URL harus menggunakan HTTPS. HTTP tidak didukung demi keamanan.

Verifikasi Sumber Webhook

Setiap webhook request dari Bukasir disertai header X-Bukasir-Webhook: 1 dan User-Agent: Bukasir-WebhookRelay/1.0. Gunakan header ini untuk memverifikasi bahwa request benar-benar berasal dari Bukasir.

Catatan Bukasir tidak mengirimkan webhook secret atau HMAC signature. Verifikasi dilakukan dengan mengecek header X-Bukasir-Webhook: 1.

PHP

<?php
$bukasirHeader = $_SERVER['HTTP_X_BUKASIR_WEBHOOK'] ?? '';

// Verifikasi sumber request
if ($bukasirHeader !== '1') {
    http_response_code(403);
    die('Unauthorized');
}

$data = json_decode(file_get_contents('php://input'), true);

$orderId     = $data['order_id'];
$status      = $data['transaction_status']; // bukan 'status'
$grossAmount = $data['gross_amount'];       // string, misal "50350.00"

echo json_encode(['success' => true]);
?>

Node.js / Express

app.post('/webhook', express.json(), (req, res) => {
  const bukasirHeader = req.headers['x-bukasir-webhook'];

  if (bukasirHeader !== '1') {
    return res.status(403).json({ error: 'Unauthorized' });
  }

  const { order_id, transaction_status, gross_amount } = req.body;

  console.log(`Payment ${order_id}: ${transaction_status}`);

  // Update database, kirim email, dll.

  res.json({ success: true });
});
Tips Keamanan Untuk keamanan tambahan, Anda juga bisa memverifikasi bahwa request berasal dari IP Bukasir atau memeriksa payload secara logis (misalnya order_id yang valid di database Anda).

Payload Format

Payload yang dikirimkan adalah notifikasi asli dari Midtrans (tanpa field signature_key). Berikut contoh payload yang akan Anda terima:

{
  "transaction_time": "2026-07-23 14:30:00",
  "transaction_status": "settlement",
  "transaction_id": "abc123-def456-ghi789",
  "status_code": "200",
  "order_id": "ORDER-001",
  "gross_amount": "50350.00",
  "payment_type": "qris",
  "fraud_status": "accept",
  "settlement_time": "2026-07-23 14:30:15"
}

Field Deskripsi

Field Type Deskripsi
order_id string Unique identifier transaksi
transaction_status string Status transaksi dari Midtrans (bukan status platform Bukasir)
transaction_id string ID transaksi dari Midtrans
status_code string HTTP-like status code dari Midtrans (misal "200")
gross_amount string Total yang dibayarkan, format string desimal (misal "50350.00")
payment_type string Metode pembayaran: qris, bank_transfer, gopay, dll.
fraud_status string Status fraud: accept, challenge, deny
transaction_time string Waktu transaksi dibuat (format: YYYY-MM-DD HH:MM:SS)
settlement_time string Waktu pembayaran diselesaikan (format: YYYY-MM-DD HH:MM:SS)
Catatan Payload dapat mengandung field tambahan lain dari Midtrans tergantung metode pembayaran. Field di atas adalah yang paling umum.

Status Pembayaran

Webhook meneruskan status dari Midtrans (transaction_status). Perhatikan bahwa ini berbeda dari status platform Bukasir (completed, expired, cancelled, failed).

Status Midtrans Deskripsi Aksi yang Disarankan
pending Menunggu pembayaran dari customer Tunggu — jangan proses pesanan
capture Transaksi berhasil di-capture (kartu kredit) Cek fraud_status sebelum proses
settlement Pembayaran berhasil diterima Proses pesanan / kirim barang
expire Pembayaran melewati batas waktu Batalkan pesanan / informasikan ke customer
cancel Transaksi dibatalkan Tidak perlu aksi tambahan
deny Pembayaran ditolak oleh provider Hubungi customer untuk pembayaran ulang
refund Transaksi di-refund Update status pesanan dan informasikan customer

Retries & Reliability

Bukasir akan melakukan retry pengiriman webhook hingga 5 kali jika server Anda tidak merespons dengan status 200. Interval retry: 1 menit, 5 menit, 30 menit, 2 jam, 24 jam.

Best Practice
  • Selalu kirim response 200 secepat mungkin. Proses berat bisa dilakukan secara async.
  • Implementasikan idempotency — webhook yang sama mungkin dikirim lebih dari sekali.
  • Gunakan database untuk mencatat order_id dan status yang sudah diproses.

Contoh Implementasi Lengkap

<?php
// webhook.php — Implementasi lengkap dengan logging

// 1. Verifikasi sumber
$bukasirHeader = $_SERVER['HTTP_X_BUKASIR_WEBHOOK'] ?? '';
if ($bukasirHeader !== '1') {
    http_response_code(403);
    error_log("[Bukasir Webhook] Unauthorized from " . $_SERVER['REMOTE_ADDR']);
    die('Forbidden');
}

$data = json_decode(file_get_contents('php://input'), true);

// Log untuk debugging
error_log("[Bukasir Webhook] Order: " . $data['order_id']
    . " Status: " . $data['transaction_status']);

// 2. Cek idempotency — jangan proses dua kali
$orderId = $data['order_id'];
$status  = $data['transaction_status'];

// 3. Proses berdasarkan status Midtrans
switch ($status) {
    case 'capture':
        // Kartu kredit — cek fraud_status
        if ($data['fraud_status'] === 'accept') {
            updateOrderStatus($orderId, 'paid');
        }
        break;

    case 'settlement':
        // Pembayaran berhasil — update order, kirim email
        updateOrderStatus($orderId, 'paid');
        sendConfirmationEmail($data);
        break;

    case 'expire':
        // Pembayaran expired — update order
        updateOrderStatus($orderId, 'expired');
        break;

    case 'cancel':
    case 'deny':
        // Dibatalkan atau ditolak
        updateOrderStatus($orderId, $status);
        break;

    case 'refund':
        // Transaksi di-refund
        updateOrderStatus($orderId, 'refunded');
        break;
}

http_response_code(200);
echo json_encode(['success' => true]);
?>