Implementasi & Pengujian
Halaman ini menjelaskan hal-hal yang perlu disiapkan pada endpoint webhook Anda agar notifikasi pembayaran diproses dengan aman dan andal.
Ketentuan Endpoint
| Ketentuan | Keterangan |
|---|---|
| Method | Menerima POST dengan body JSON. |
| Akses publik | Dapat diakses dari internet tanpa login, cookie, atau proteksi CSRF. Jika framework Anda memakai CSRF (misalnya Laravel), kecualikan route webhook dari pemeriksaan CSRF. |
| HTTPS | Sangat disarankan, dengan sertifikat SSL yang valid (bukan self-signed). |
| Redirect | Hindari redirect (301/302). Daftarkan URL tujuan akhir, termasuk https:// dan ada/tidaknya garis miring di akhir. |
| Waktu respons | Balas dalam waktu kurang dari 30 detik. Disarankan di bawah 5 detik. |
| Status respons | Balas 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) atauhash_equals(PHP). - Jika token tidak cocok, balas
401dan 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(atauva_number) sebagai kunci unik. Simpan setiaptrx_idyang sudah diproses. - Jika
trx_idsudah pernah diproses, tetap balas HTTP 200 tanpa memproses ulang, agar AlnairPay berhenti mengirim ulang. - Cocokkan
merchant_refdengan pesanan di sistem Anda, dan periksaamountsama 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.
- Simpan URL Webhook terlebih dahulu. Pengujian selalu dikirim ke URL yang tersimpan.
- Ubah isi payload bila perlu, atau klik Isi dari VA terakhir untuk memakai data pembayaran terakhir akun Anda.
- Klik Kirim Tes. Dashboard menampilkan HTTP status, durasi, response body, dan header yang dikirim.
- 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.
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-Tokenyang salah. - Endpoint membalas HTTP 200 (bukan 201/204) untuk notifikasi yang valid.
- Notifikasi dengan
trx_idyang sama tidak diproses dua kali, dan tetap dibalas HTTP 200. -
merchant_refdanamountdicocokkan dengan pesanan sebelum ditandai lunas. - Endpoint merespons dalam kurang dari 30 detik.
- Hasil Uji Webhook di Dashboard menunjukkan HTTP 200.