Skip to main content

Implementasi & Pengujian

Halaman ini menjelaskan hal-hal yang perlu disiapkan pada endpoint webhook Anda agar notifikasi pembayaran diproses dengan aman dan andal.

Ketentuan Endpoint​

KetentuanKeterangan
MethodMenerima POST dengan body JSON.
Akses publikDapat diakses dari internet tanpa login, cookie, atau proteksi CSRF. Jika framework Anda memakai CSRF (misalnya Laravel), kecualikan route webhook dari pemeriksaan CSRF.
HTTPSSangat disarankan, dengan sertifikat SSL yang valid (bukan self-signed).
RedirectHindari redirect (301/302). Daftarkan URL tujuan akhir, termasuk https:// dan ada/tidaknya garis miring di akhir.
Waktu responsBalas dalam waktu kurang dari 30 detik. Disarankan di bawah 5 detik.
Status responsBalas HTTP 200 setelah notifikasi diterima.

Verifikasi Token​

Setiap webhook membawa header X-webhook-Token yang berisi Token Verifikasi akun Anda. Tolak request yang tokennya tidak cocok, karena siapa pun dapat mengirim request ke URL webhook Anda.

  • Simpan Token Verifikasi di konfigurasi server (environment variable), jangan di kode sumber atau aplikasi frontend.
  • Bandingkan token dengan fungsi perbandingan yang aman terhadap timing attack, misalnya crypto.timingSafeEqual (Node.js) atau hash_equals (PHP).
  • Jika token tidak cocok, balas 401 dan jangan proses pembayaran.

Idempotensi​

Notifikasi yang sama bisa diterima lebih dari sekali (lihat Retry). Pastikan memproses notifikasi yang sama dua kali tidak menyebabkan pesanan dikirim dua kali atau saldo bertambah dua kali.

  • Gunakan trx_id (atau va_number) sebagai kunci unik. Simpan setiap trx_id yang sudah diproses.
  • Jika trx_id sudah pernah diproses, tetap balas HTTP 200 tanpa memproses ulang, agar AlnairPay berhenti mengirim ulang.
  • Cocokkan merchant_ref dengan pesanan di sistem Anda, dan periksa amount sama dengan nominal tagihan sebelum menandai pesanan lunas.

Proses Cepat, Kerjakan Berat Belakangan​

Balas HTTP 200 secepat mungkin. Pekerjaan yang lama, seperti mengirim email, membuat faktur PDF, atau memanggil sistem lain, sebaiknya dijalankan di background job setelah data pembayaran tersimpan. Jika endpoint terlalu lama merespons (lebih dari 30 detik), AlnairPay menganggapnya gagal dan mengirim ulang notifikasi.

Contoh Implementasi​

Node.js (Express)​

import express from 'express'
import crypto from 'node:crypto'

const app = express()
app.use(express.json())

const WEBHOOK_TOKEN = process.env.ALNAIRPAY_WEBHOOK_TOKEN

function isValidToken(received) {
const a = Buffer.from(String(received ?? ''))
const b = Buffer.from(WEBHOOK_TOKEN)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}

app.post('/webhook/alnairpay', async (req, res) => {
// 1. Verifikasi token
if (!isValidToken(req.get('X-webhook-Token'))) {
return res.status(401).json({ success: false, message: 'Invalid token' })
}

const { merchant_ref, trx_id, amount, status } = req.body

// 2. Abaikan notifikasi yang sudah pernah diproses, tetap balas 200
if (await db.payments.exists({ trx_id })) {
return res.status(200).json({ success: true, duplicate: true })
}

// 3. Cocokkan dengan pesanan dan nominal
const order = await db.orders.findByRef(merchant_ref)
if (!order || order.amount !== Number(amount) || status !== 'PAID') {
// simpan untuk pengecekan manual; tetap 200 agar tidak dikirim ulang terus-menerus
await db.webhookIssues.insert({ body: req.body })
return res.status(200).json({ success: true, review: true })
}

// 4. Tandai lunas, lalu jalankan pekerjaan berat di background
await db.payments.insert({ trx_id, merchant_ref, amount })
await db.orders.markPaid(order.id)
queue.add('send-receipt', { orderId: order.id })

return res.status(200).json({ success: true })
})

PHP (Native)​

<?php
$expectedToken = getenv('ALNAIRPAY_WEBHOOK_TOKEN');
$receivedToken = $_SERVER['HTTP_X_WEBHOOK_TOKEN'] ?? '';

// 1. Verifikasi token
if (!hash_equals($expectedToken, $receivedToken)) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Invalid token']);
exit;
}

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

// 2. Abaikan notifikasi yang sudah pernah diproses, tetap balas 200
if (paymentAlreadyProcessed($payload['trx_id'])) {
http_response_code(200);
echo json_encode(['success' => true, 'duplicate' => true]);
exit;
}

// 3. Cocokkan pesanan & nominal, lalu tandai lunas
markOrderAsPaid($payload['merchant_ref'], $payload['trx_id'], $payload['amount']);

http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['success' => true]);

Menguji Endpoint​

Gunakan fitur Uji Webhook di Dashboard Client (Settings → Webhook) untuk memastikan endpoint Anda siap sebelum menerima pembayaran sungguhan.

  1. Simpan URL Webhook terlebih dahulu. Pengujian selalu dikirim ke URL yang tersimpan.
  2. Ubah isi payload bila perlu, atau klik Isi dari VA terakhir untuk memakai data pembayaran terakhir akun Anda.
  3. Klik Kirim Tes. Dashboard menampilkan HTTP status, durasi, response body, dan header yang dikirim.
  4. Pastikan hasilnya HTTP 200.

Notifikasi uji membawa header tambahan X-Webhook-Test: true. Notifikasi pembayaran sungguhan tidak memiliki header ini. Gunakan header tersebut agar sistem Anda tidak memproses pesanan fiktif dari pengujian, namun tetap membalas HTTP 200.

note

Notifikasi uji dikirim satu kali tanpa retry, dengan batas waktu 15 detik, dan dibatasi 10 pengujian per menit.

Checklist Sebelum Go-Live​

  • URL Webhook sudah disimpan di Dashboard dan dapat diakses dari internet (HTTPS).
  • Endpoint menolak request dengan X-webhook-Token yang salah.
  • Endpoint membalas HTTP 200 (bukan 201/204) untuk notifikasi yang valid.
  • Notifikasi dengan trx_id yang sama tidak diproses dua kali, dan tetap dibalas HTTP 200.
  • merchant_ref dan amount dicocokkan dengan pesanan sebelum ditandai lunas.
  • Endpoint merespons dalam kurang dari 30 detik.
  • Hasil Uji Webhook di Dashboard menunjukkan HTTP 200.