Webhook-nya Telat, Job Rekonsiliasinya Keburu Jalan

Jalur webhook sudah idempotent. Lalu ada job rekonsiliasi pagi, dan satu pembayaran tercatat dua kali dari dua jalur yang dua-duanya benar. Coba tebak kuncinya salah di mana.

Gateway-nya down semalam. Pembayaran yang terjadi di sisi mereka nggak ada yang sampai ke kita lewat webhook, dan pagi harinya job rekonsiliasi membaca laporan settlement hari itu lalu menutup yang belum tercatat. Sampai di sini semua benar — job-nya bekerja persis seperti yang dimaksud.

Lalu jam sebelas siang, gateway mengirim ulang webhook yang belum pernah dijawab. Dan satu pembayaran yang sama tercatat dua kali: satu dari job, satu dari webhook.

Bukan karena salah satunya rusak. Jalur webhook-nya sudah idempotent — dia jawaban lab sebelumnya, lengkap dengan klaim per event dan jawaban yang disimpan. Job-nya juga benar untuk pertanyaan yang dia jawab. Masalahnya cuma satu: keduanya memakai kunci yang berbeda, dan nggak ada satu pun dari keduanya yang tahu bahwa jalur satunya ada.

kondisi awal
3 webhook malam itu, lalu job rekonsiliasi paginya 6 pembayaran jadi 9 baris — 3 di antaranya punya dua baris, satu dari tiap jalur
webhook yang telat akhirnya sampai 6 pembayaran jadi 12 baris; saldo 665.000 dari 220.000 yang seharusnya
job rekonsiliasi dijalankan dua kali 6 baris baru tiap kali, dan job-nya melaporkan inserted=6 — bukan 0
dua run job jalan bersamaan 12 baris untuk 6 pembayaran
laporan settlement beda nominal dengan yang sudah tercatat 2 baris untuk 1 pembayaran, dan mismatched tetap kosong

Perhatikan baris ketiga. Job-nya bukan cuma menulis dobel — dia melaporkan bahwa dia menulis enam baris baru, dengan yakin, untuk hari yang sebenarnya sudah selesai.

Di kerjaan pertama saya jalur keduanya bukan job rekonsiliasi, tapi panggilan balik ke backend storefront yang beda — dan bentuk masalahnya persis sama: satu fakta, dua jalur, dua kunci yang nggak pernah bertemu. Ceritanya di sini.

Ini sudut kedua dari tiga sudut yang saya rapikan di situ:

  1. Datang lebih dari sekali → lab sebelumnya.
  2. Datang dari dua jalur → lab ini.
  3. Datang tidak berurutan → yang bikin stok gudang saya salah; bentuknya beda dari lab ini.

Akarnya satu: keputusan "sudah pernah diterapkan atau belum" diambil berdasarkan kedatangan pesannya, di aplikasi — bukan di database, dengan kunci dan patokan waktu yang dimiliki faktanya sendiri.

Poin Penting

Dua jalur yang sama-sama benar tetap bisa mencatat satu pembayaran dua kali.

  • Jalur webhook memakai kunci milik pesannya; job rekonsiliasi membuat kuncinya sendiri — dan keduanya nggak pernah bertemu.
  • Job yang dijalankan dua kali melaporkan inserted=6 dua kali, dengan yakin, untuk hari yang sudah selesai.
  • Kunci yang benar bukan kunci yang unik, tapi kunci yang dimiliki oleh faktanya, bukan oleh pesannya.
  • Dua jalur boleh punya kebijakan berbeda waktu menemukan keanehan — penunggunya memang berbeda.

Misi lab-nya

Tiga syarat:

  1. Satu pembayaran, satu baris. Dari jalur mana pun, dan berapa kali pun jalurnya dipanggil.
  2. Job boleh dijalankan lagi, dan laporannya jujur. Run kedua untuk hari yang sama nggak menambah baris dan melaporkan inserted=0. Run yang cuma sanggup memproses sebagian boleh dilanjutkan kapan saja. Dua run yang tumpang tindih juga nggak boleh mendobel.
  3. Nominal yang sudah tercatat nggak berubah diam-diam. Kalau laporan settlement menyebut nominal yang berbeda, barisnya tetap satu, nominalnya tetap yang pertama, dan perbedaannya dilaporkan — bukan ditelan.

Kenapa ini layak dicoba

Di lab sebelumnya, "pembayaran" dan "pesan yang memberitahukan pembayaran itu" selalu datang berpasangan, jadi nggak pernah ada bedanya. Begitu ada jalur kedua, bedanya jadi hal paling penting di seluruh sistem: kunci yang benar bukan kunci yang unik, tapi kunci yang dimiliki oleh faktanya — bukan oleh pesannya.

Dan bagian kedua yang lebih halus: dua jalur yang sama-sama benar bisa butuh kebijakan yang berbeda waktu menemukan keanehan. Webhook boleh membalas 409 untuk satu event yang aneh; job rekonsiliasi nggak boleh mati cuma karena satu baris laporan nggak cocok.

Kalau kamu mau mencobanya

BASH
git clone https://github.com/izzudd/inva-lab.git
cd inva-lab/reconciliation
make up      # database + API + gateway tiruan
make demo    # cerita lengkapnya: webhook malam itu, job paginya, webhook yang telat
make check   # target: 24 pemeriksaan, semua hijau

Gateway-nya tiruan dan laporannya dibangkitkan dari tanggalnya, jadi kamu bisa pakai tanggal apa pun: make demo DAY=2024-03-17.

Pertanyaannya

Job rekonsiliasi itu jalur kedua yang menerima satu fakta lewat dua kunci berbeda. Sebelum menyentuh kode, jawab ini: kunci mana yang berasal dari pembayarannya, dan kunci mana yang berasal dari pesan yang memberitahukannya? Dan yang kedua: kenapa job-nya harus membuat kuncinya sendiri, padahal jalur webhook sudah punya kunci yang unik?

Jawaban lengkapnya, termasuk kenapa menghapus id run dari kuncinya juga menghapus kebutuhan akan kursor, ada di artikel jawabannya.

Biar makin jago ngoding.

Yuk gabung bareng temen-temen developer lainnya buat dapet update teknologi, tips, dan tutorial santai tiap minggu.

Biar Nggak Kudet

Update teknologi, tips ngoding, dan insight santai langsung ke email kamu tiap minggu.

Santai, anti spam. Bisa berhenti langganan kapan aja.

Baca Selanjutnya

Lihat semua

© 2026 Inva.dev.