Integrasi Payment Gateway Indonesia: Menangani Webhook Midtrans dan Xendit dengan Jaminan Idempoten

Mencegah pesanan ganda dan celah finansial: verifikasi signature SHA-512 Midtrans dan callback token Xendit, arsitektur antrean webhook asinkron, dan penguncian transaksi idempoten PostgreSQL.

PuluhLabs Engineering Team, Staff Payment Platform Engineer · 9 November 2026 · 7 min baca

Integrasi Payment Gateway Indonesia: Menangani Webhook Midtrans dan Xendit dengan Jaminan Idempoten
Pengembangan Web
DECOUPLED

Isi artikel ini

  • 1. Verifikasi Signature Kriptografis
  • 2. Pola Penanganan Asinkron Berkecepatan Tinggi
  • 3. Menjamin Idempotensi dengan Database Locking
  • 4. Mekanisme Rekonsiliasi Cadangan (Fallback Polling Job)

Dalam ekosistem perdagangan digital dan aplikasi B2B di Indonesia, integrasi sistem pembayaran multi-saluran (QRIS, Virtual Account bank nasional, e-wallet, hingga kartu kredit) umumnya bertumpu pada penyedia gerbang pembayaran (payment gateway) seperti Midtrans dan Xendit.

Meskipun integrasi antarmuka checkout tampak mudah dengan memanfaatkan SDK popup bawaan, tantangan rekayasa perangkat lunak sesungguhnya terletak pada penanganan Webhook Notification (HTTP Callback) di sisi backend.

Jaringan internet publik tidak pernah dapat diandalkan secara sempurna (unreliable network). Server payment gateway dapat mengirimkan satu notifikasi pembayaran yang sama sebanyak tiga atau empat kali akibat timeout sementara pada koneksi HTTP. Jika backend Anda tidak dirancang dengan prinsip Idempotensi dan verifikasi kriptografis yang tangguh, sistem Anda berisiko memproses pesanan ganda, mengisi saldo kredit pengguna dua kali, atau mengalami benturan status transaksi (race condition).

text
Alur Penanganan Webhook Idempoten Payment Gateway:
[Penyedia Payment Gateway (Midtrans / Xendit)]
               │
               ▼ POST /api/webhooks/payment (Membawa Signature Hash)
┌─────────────────────────────────────────────────────────────┐
│  API Gateway / HTTP Controller                              │
│  1. Verifikasi Signature Kriptografis (SHA-512 / HMAC)      │
│  2. Jika Signature Tidak Cocok ────> [Tolak: HTTP 401/403]  │
│  3. Simpan Payload ke Antrean Redis Message Queue           │
│  4. Balas Segera ke Gateway: HTTP 200 OK (< 200 milidetik)  │
└──────────────────────────────┬──────────────────────────────┘
                               │
                               ▼ (Asynchronous Processing)
┌─────────────────────────────────────────────────────────────┐
│  Background Worker (NestJS / BullMQ / Go Worker)            │
│  1. Buka PostgreSQL Transaction dengan Lock Eksklusif       │
│  2. Cek Tabel Idempotency: Apakah Transaction ID sudah ada? │
│     ├── SUDAH PROSES  ──> Abaikan Payload Duplikat          │
│     └── BELUM PROSES ──> Perbarui Status Pesanan ke PAID    │
│  3. Terbitkan Faktur Pajak & Kirim Email Notifikasi         │
└─────────────────────────────────────────────────────────────┘

1. Verifikasi Signature Kriptografis #

Langkah keamanan paling mendasar sebelum membaca isi data transaksi adalah memvalidasi keaslian pengirim notifikasi. Tanpa verifikasi tanda tangan (signature verification), pihak penyerang dapat menembak endpoint webhook Anda secara langsung dan memalsukan status pembayaran menjadi settlement atau PAID.

A. Verifikasi Signature Midtrans (SHA-512) #

Midtrans menyertakan nilai signature_key pada payload notifikasi JSON. Nilai ini dihasilkan dari penggabungan string data spesifik dengan Server Key rahasia:

$$\text{Signature} = \text{SHA512}(\text{order_id} + \text{status_code} + \text{gross_amount} + \text{ServerKey})$$

Implementasi verifikasi pada Node.js:

typescript
import crypto from 'crypto';

export function verifyMidtransSignature(payload: {
  order_id: string;
  status_code: string;
  gross_amount: string;
  signature_key: string;
}, serverKey: string): boolean {
  const rawString = `${payload.order_id}${payload.status_code}${payload.gross_amount}${serverKey}`;
  const calculatedHash = crypto.createHash('sha512').update(rawString).digest('hex');

  // Gunakan timingSafeEqual untuk mencegah serangan timing-attack
  return crypto.timingSafeEqual(
    Buffer.from(calculatedHash),
    Buffer.from(payload.signature_key)
  );
}

B. Verifikasi Webhook Token Xendit #

Xendit mengirimkan token verifikasi pada header HTTP x-callback-token:

typescript
export function verifyXenditCallbackToken(
  incomingToken: string | undefined,
  expectedToken: string
): boolean {
  if (!incomingToken) return false;
  return crypto.timingSafeEqual(
    Buffer.from(incomingToken),
    Buffer.from(expectedToken)
  );
}

2. Pola Penanganan Asinkron Berkecepatan Tinggi #

Banyak penyedia payment gateway menerapkan batas waktu tunggu respons (HTTP timeout) yang sangat ketat, biasanya antara 5 hingga 10 detik. Jika endpoint backend Anda mencoba memverifikasi pembayaran, memperbarui lima tabel database, memicu pembuatan berkas PDF faktur, dan memanggil API email pihak ketiga dalam satu siklus request HTTP yang sama, koneksi berisiko mengalami timeout.

Kegagalan respons dalam batas waktu tersebut akan memicu algoritma retry otomatis dari gateway, membanjiri server Anda dengan request duplikat.

Aturan emas penanganan webhook: Balas HTTP 200 OK secepat mungkin setelah payload diverifikasi dan disimpan ke antrean antrean pesan (message queue) terisolasi.

typescript
// app/api/webhooks/midtrans/route.ts
import { NextResponse } from 'next/server';
import { paymentQueue } from '@/lib/queue';
import { verifyMidtransSignature } from '@/lib/payment-security';

export async function POST(request: Request) {
  const payload = await request.json();

  // 1. Validasi Tanda Tangan Kriptografis
  const isValid = verifyMidtransSignature(payload, process.env.MIDTRANS_SERVER_KEY!);
  if (!isValid) {
    return NextResponse.json({ error: 'Invalid Signature Key' }, { status: 403 });
  }

  // 2. Masukkan ke BullMQ Queue untuk diproses di latar belakang
  await paymentQueue.add('process_payment_status', payload, {
    attempts: 5,
    backoff: { type: 'exponential', delay: 2000 },
    removeOnComplete: true,
  });

  // 3. Konfirmasi penerimaan segera ke Payment Gateway
  return NextResponse.json({ status: 'OK', message: 'Webhook received' });
}

3. Menjamin Idempotensi dengan Database Locking #

Idempotensi adalah sifat di mana sebuah operasi dapat dieksekusi berulang kali dengan hasil akhir yang sama persis seperti saat pertama kali dijalankan, tanpa efek samping tambahan.

Untuk mencegah race condition saat dua webhook dari gateway yang sama tiba dalam selisih milidetik, backend worker wajib memanfaatkan transaksi database PostgreSQL dengan mekanisme penguncian baris eksplisit (SELECT ... FOR UPDATE):

sql
-- Skema Tabel Idempotency Ledger
CREATE TABLE payment_webhook_logs (
    id SERIAL PRIMARY KEY,
    transaction_id VARCHAR(100) NOT NULL UNIQUE,
    order_id VARCHAR(100) NOT NULL,
    payment_status VARCHAR(50) NOT NULL,
    payload JSONB NOT NULL,
    processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Logika eksekusi di dalam worker:

typescript
// workers/payment.worker.ts
import { pool } from '@/lib/db';

export async function processPaymentTransaction(payload: any) {
  const client = await pool.connect();

  try {
    await client.query('BEGIN');

    // 1. Kunci baris pesanan untuk mencegah proses paralel
    const orderRes = await client.query(
      'SELECT id, status, total_amount FROM orders WHERE id = $1 FOR UPDATE',
      [payload.order_id]
    );

    if (orderRes.rows.length === 0) {
      await client.query('ROLLBACK');
      return;
    }

    const currentOrder = orderRes.rows[0];

    // Jika pesanan sudah berstatus PAID sebelumnya, abaikan webhook ini (Idempoten)
    if (currentOrder.status === 'PAID') {
      await client.query('ROLLBACK');
      console.log(`[Idempotent Skip] Pesanan ${payload.order_id} sudah pernah dibayar.`);
      return;
    }

    // 2. Evaluasi status transaksi dari gateway
    if (payload.transaction_status === 'settlement' || payload.transaction_status === 'capture') {
      // Perbarui status pesanan menjadi LUNAS
      await client.query(
        'UPDATE orders SET status = $1, payment_method = $2, paid_at = NOW() WHERE id = $3',
        ['PAID', payload.payment_type, payload.order_id]
      );

      // Catat ke log idempotensi
      await client.query(
        'INSERT INTO payment_webhook_logs (transaction_id, order_id, payment_status, payload) VALUES ($1, $2, $3, $4)',
        [payload.transaction_id, payload.order_id, 'PAID', JSON.stringify(payload)]
      );
    }

    await client.query('COMMIT');
  } catch (err) {
    await client.query('ROLLBACK');
    throw err; // Memicu retry otomatis BullMQ
  } finally {
    client.release();
  }
}

4. Mekanisme Rekonsiliasi Cadangan (Fallback Polling Job) #

Meskipun sistem webhook telah dirancang dengan sempurna, terdapat skenario ekstrem di mana penyedia gateway gagal mengirimkan callback akibat gangguan rute routing BGP telekomunikasi.

Sebagai jaring pengaman terakhir, aplikasi enterprise wajib menyertakan Cron Polling Job berkala (misalnya setiap 15 menit):

  • Cari seluruh pesanan yang masih berstatus PENDING selama lebih dari 30 menit.
  • Hubungi API pengecekan status Midtrans (GET /v2/{order_id}/status) atau Xendit (GET /v2/invoices/{invoice_id}).
  • Jika status di payment gateway telah sukses, perbarui database menggunakan fungsi idempoten yang sama.

Dengan memadukan validasi tanda tangan kriptografis, arsitektur antrean asinkron, penguncian transaksi database idempoten, dan job rekonsiliasi cadangan, sistem penagihan aplikasi Anda akan beroperasi dengan integritas finansial tanpa cela.

Bagikan Artikel Ini:
Topik Terkait:Payment GatewayMidtransXenditIdempotencyFintech
Istilah Teknis Terkait (Knowledge Graph Glossary)
Jejaring Entitas & Solusi Teknis Terkait
3 Solusi Terintegrasi
Architecture Matrix

Perbandingan Headless CMS vs Monolitik

Kajian komparatif performa, keamanan OWASP, dan total biaya kepemilikan (TCO).

Buka Solusi
Planning Tool

Kalkulator Investasi & Jadwal Website

Estimasi biaya pembuatan website company profile, portal B2B, atau web app kustom.

Buka Solusi
Partnership Hub

Kemitraan White-Label untuk Agensi

Dukungan rekayasa teknis rahasia di balik layar agensi Anda dengan NDA resmi.

Buka Solusi
TO
PuluhLabs Engineering TeamPenulis Terverifikasi

Staff Payment Platform Engineer

Praktisi dan konsultan pengembangan platform digital terpercaya dengan pengalaman memimpin perancangan lebih dari 50+ proyek untuk skala korporat, bisnis B2B, dan instansi profesional. Berfokus pada pembangunan solusi web yang tangguh, cepat, aman, serta dirancang untuk mendorong pertumbuhan bisnis jangka panjang.

Ditinjau & divalidasi oleh: Tim Profesional PuluhLabs
Terakhir ditinjau: 9 November 2026

Ingin Menerapkan Standar Performa Ini pada Proyek Agensi Anda?

Tim PuluhLabs siap menjadi mitra rekayasa digital rahasia (white-label) untuk agensi Anda. Garansi performa skor Lighthouse 90+, Core Web Vitals hijau, dan 100% hak milik kode.

Konsultasi Proyek Bersama Tim PuluhLabs

Artikel Rekomendasi Selanjutnya

Lihat Semua
  1. Pengembangan Web

    Arsitektur Micro-Frontends vs Modular Monolith: Panduan Memilih untuk Aplikasi Web Skala Enterprise

  2. Pengembangan Web

    Implementasi Zero-Trust Architecture pada REST API dan Webhook Korporat

  3. Pengembangan Web

    Optimasi Query PostgreSQL Skala Jutaan Baris: Indexing, Table Partitioning, dan Connection Pooling