Webhook Pembayaran: Cegah Data Hilang dan Dobel
Verifikasi pengirim, jawab cepat, cegah pemrosesan ganda, dan cocokkan catatan agar pemberitahuan pembayaran dapat diandalkan.
Untuk Tim Teknis

Daftar isi
Webhook adalah pemberitahuan otomatis antarsistem saat terjadi perubahan. Penyedia pembayaran dan platform pemesanan memakainya untuk mengabarkan pembayaran berhasil, reservasi terkonfirmasi, perpanjangan langganan, atau pesanan terpenuhi. Catatan dari kiriman ini perlu diproses secara andal dan dicocokkan dengan sistem sumber.
Banyak sistem yang saya tinjau belum memiliki penanganan ketika pemberitahuan tidak diterima. Pengiriman dicoba, tetapi kegagalan tidak ditelusuri sampai tuntas.
Di celah itulah pengembalian dana terlewat, reservasi tidak pernah terkonfirmasi, dan tiket dukungan menumpuk menanyakan kenapa pelanggan sudah bayar tapi sistem tidak tahu. Terapkan pemeriksaan pengirim, penyimpanan yang andal, pencegahan duplikasi, dan pencocokan catatan sebagai bagian utama integrasi.
Verifikasi dulu sebelum percaya isi kirimannya
Kesalahan pertama adalah memproses isi webhook sebelum memastikan siapa pengirimnya. Siapa pun yang tahu URL titik penerima sistem bisa mengirim POST palsu berisi kejadian "payment.succeeded". Penyedia yang dibahas di sini menyertakan signature — tanda tangan digital — di tiap kiriman, persis seperti stempel lilin di surat lama — bukan mencegah orang membaca isinya, tapi membuktikan segelnya asli dan belum diutak-atik di jalan.
Stripe mengirim header Stripe-Signature berisi penanda waktu dan kode autentikasi HMAC-SHA256 yang dihitung dari isi permintaan asli. Pustaka resmi Stripe menerapkan toleransi bawaan 5 menit antara penanda waktu dan waktu saat ini untuk mencegah serangan pemutaran ulang (replay attack). Jika verifikasi dilakukan manual, perbandingannya harus constant-time, dengan waktu proses yang tidak bergantung pada letak perbedaan nilai: perbandingan == biasa pada string tanda tangan digital membocorkan informasi waktu yang bisa dieksploitasi penyerang. Header X-Hub-Signature-256 milik GitHub bekerja dengan cara serupa, dan dokumennya eksplisit: gunakan fungsi constant-time seperti crypto.timingSafeEqual, jangan pernah pakai perbandingan kesamaan biasa.
Header X-Shopify-Hmac-Sha256 milik Shopify mengikuti pola identik: HMAC-SHA256 yang dikodekan dalam base64 dari isi asli sebelum penguraian, dihitung dengan kunci rahasia aplikasi terkait. Detail "isi asli sebelum penguraian" ini kelihatan sepele, padahal menentukan. Kalau kerangka aplikasi yang digunakan sudah mengurai JSON sebelum pengecekan tanda tangan digital berjalan, hash yang dihitung sering kali berasal dari muatan data yang sudah dibentuk kembali menjadi teks.
Perubahan representasi JSON dapat membuat nilai hash berbeda dari kiriman asli. Verifikasi bita mentah sebelum komponen perantara mengubah isinya.
Simpan kiriman lalu jawab cepat sebelum memprosesnya
Penyedia tidak menunggu tanpa batas waktu. Stripe akan menghentikan penantian kalau responsnya terlalu lama. Shopify mengharapkan respons dalam kira-kira 5 detik. GitHub memberi 10 detik.
Kalau seluruh proses pemenuhan pesanan dijalankan sebelum respons dikirim (validasi penagihan, pembaruan persediaan, surel konfirmasi), cepat atau lambat batas waktu itu kelewat saat lalu lintas tinggi. Dan dari sisi penyedia, titik penerima yang lambat kelihatan sama persis dengan titik penerima yang rusak.
Panduan resmi Stripe: kembalikan respons 2xx dulu, baru jalankan logika kompleksnya secara asinkron lewat antrean melalui proses terpisah dari penanganan permintaan. Pemroses permintaan memiliki tiga tugas: verifikasi tanda tangan digital, simpan kejadian asli ke antrean atau tabel, kembalikan 200. Proses pekerja kemudian mengambil catatan dari antrean dan menjalankan pekerjaan bisnis. Kirim respons berhasil hanya setelah kejadian tersimpan secara tahan gangguan. Jika penyimpanan gagal, biarkan penyedia mencoba lagi. Respons dapat tetap cepat selama antrean memiliki kapasitas; pantau juga kapasitas dan usia pekerjaan yang belum diproses.
Cegah pemrosesan ganda dengan ID kejadian
Pengiriman ulang merupakan bagian dari rancangan protokol, sehingga penerima harus siap menerima kejadian yang sama lebih dari sekali. Stripe secara eksplisit memperingatkan bahwa titik penerima bisa menerima kejadian yang sama lebih dari sekali. Stripe merekomendasikan mencatat ID kejadian yang sudah diproses, lalu melewati apa pun yang sudah tercatat. Shopify memberi alat yang sama lewat header X-Shopify-Webhook-Id, dibuat khusus untuk mendeteksi dan melewati pengiriman duplikat.
Lewatkan langkah ini, dan kejadian payment.succeeded yang dikirim ulang memenuhi pesanan yang sama dua kali, atau konfirmasi reservasi mengirim dua surel duplikat ke tamu yang sama. Gunakan tabel idempotensi dengan ID kejadian sebagai kunci. Pengambilalihan pekerjaan harus atomik, dan status selesai dicatat bersama perubahan basis data. Bedakan kejadian yang menunggu, sedang diproses, dan selesai agar proses yang berhenti setelah penerimaan tidak membuat pekerjaan terlewati. Lindungi efek lanjutan dengan kunci operasi bisnis yang sesuai, karena ID kejadian berbeda kadang merujuk operasi yang sama. Kiriman yang telah ditangani tidak perlu menjalankan ulang pekerjaan bisnis; respons berhasil tetap dikembalikan agar penyedia berhenti mencoba.
Pencegahan duplikasi perlu menjadi bagian dari lapisan yang mengelola pengelolaan versi API dan kontrak integrasi: agar perilakunya terdokumentasi dalam kontrak integrasi.
Yang terjadi di sisi pengirim
Semua di atas berlaku saat Anda jadi penerima webhook. Kalau sistem Anda yang jadi pengirim — misalnya memberi tahu sistem mitra saat reservasi dikonfirmasi — desain pengiriman ulangnya jadi tanggung jawab Anda sendiri. Dua ujung ekstremnya bisa jadi patokan.
Stripe melakukan pengiriman ulang dengan jeda yang bertambah secara eksponensial sampai tiga hari di mode produksi — jeda antar-percobaannya makin lama — sambil membuat ulang tanda tangan digital dan penanda waktu di setiap percobaan. Shopify melakukan pengiriman ulang sampai 8 kali dalam 4 jam. Setelah 8 kegagalan berturut-turut, Shopify otomatis menghapus langganan webhook yang dibuat lewat Admin API-nya dan mengirim peringatan ke kontak pengembang untuk keadaan darurat. Kedua penyedia memberi titik penerima yang bermasalah waktu berjam-jam sampai berhari-hari untuk pulih, lalu berhenti dan membuat kegagalannya terlihat, bukan pengiriman ulang selamanya.
GitHub sengaja mengambil posisi sebaliknya. GitHub tidak otomatis mengirim ulang webhook yang gagal. Dokumennya menyatakan ini secara langsung dan hanya menawarkan pengiriman ulang melalui antarmuka pengguna, atau skrip sendiri yang memeriksa dan mengirim ulang kiriman gagal. Ujung yang satu ini jadi peringatan bagi siapa pun yang membangun pengirim webhook sendiri.
Tanpa jadwal jeda yang bertambah yang disengaja dan daftar kegagalan akhir — tampungan kiriman yang gagal terus sampai jatah pengiriman ulangnya habis — satu kiriman yang gagal hilang begitu saja. Tidak ada yang tahu sampai pelanggan bertanya ke mana konfirmasinya.
Simpan kiriman yang telah menghabiskan jatah percobaan pada daftar kegagalan akhir atau dead-letter. Operator perlu dapat mencari, memeriksa, dan mengirim ulang catatan tersebut.
Cocokkan catatan untuk menemukan kiriman yang terlewat
Sekalipun sudah ada tanda tangan digital, respons cepat, pencegahan duplikasi, dan pengiriman ulang yang andal, webhook saja bukan jaminan yang tahan lama. Jaringan dapat terputus, layanan penerima dapat dipasang ulang saat pengiriman berlangsung, dan antrean bisa menumpuk lebih lama dari rentang pengiriman ulang penyedia. Stripe juga menyarankan untuk tidak mengandalkan webhook sebagai satu-satunya sumber kebenaran.
Pola rekonsiliasi resminya adalah pemeriksaan berkala melalui List Events API dengan delivery_success=false untuk menangkap apa pun yang gagal terkirim. Catatannya, API ini hanya mengembalikan kejadian dari 30 hari terakhir. Jadwalkan pemeriksaan sebelum rentang data yang tersedia terlewati.
Versi praktis untuk kebanyakan tim: tugas yang jalan tiap malam, mengambil daftar penagihan atau reservasi terbaru dari API penyedia dan membandingkannya dengan yang tercatat di sistem sendiri. Apa pun yang ada di sisi penyedia tapi hilang di sistem sendiri ditandai dan diproses ulang oleh operator. Ini persis disiplin operasional yang menjaga kanal seperti konfirmasi reservasi lewat WhatsApp tetap bisa dipercaya. Pesan baru dikirim setelah kejadian yang mendasarinya benar-benar sampai dan diproses, bukan sekadar karena kiriman webhook terpicu.
Empat hal yang menjaga uang tetap beres
- Verifikasi tanda tangan digital terhadap isi asli sebelum penguraian, dengan perbandingan constant-time dan batas toleransi penanda waktu jika penyedia menyertakannya dalam data yang diautentikasi.
- Kunci idempotensi atau pemeriksaan ID kejadian di setiap pemroses, agar pengiriman ulang tidak pernah memproses dua kali.
- Dasbor kegagalan akhir untuk apa pun yang menghabiskan jatah pengiriman ulangnya, bukan hilang begitu saja tanpa jejak.
- Tugas rekonsiliasi tiap malam yang memperlakukan API penyedia sebagai sumber kebenaran dan log webhook sebagai salinan sementara yang mungkin punya celah.
Jadwalkan keempat kontrol tersebut dalam pekerjaan integrasi, termasuk penanggung jawab daftar kegagalan dan rekonsiliasi. Jika dilewatkan, kesalahan dapat muncul sebagai tiket dukungan, selisih buku besar, atau pengembalian dana yang sulit ditelusuri.
Artikel Terkait
Perlu bantuan menerapkan solusi ini?
Integrasi Pembayaran & API
Hubungkan layanan pembayaran dengan sistem bisnis Anda. Susun pelacakan transaksi, penanganan pembayaran bermasalah, dan pencocokan pencatatan.
Lihat layanan terkaitCeritakan kendala operasional Anda agar kita dapat membahas langkah yang sesuai.