Blog · Terbit · Diperbarui

Webhook tidak sampai: memisahkan belum pernah dikirim dari ditolak

Pembayaran berstatus succeeded, uangnya masuk, dan pesanan di sistem toko tidak pernah berubah. Reaksi yang paling sering adalah membuka kode handler dan membaca ulang verifikasi tanda tangan. Itu langkah kedua, bukan pertama. Langkah pertama adalah menjawab satu pertanyaan yang memisahkan seluruh kemungkinan menjadi dua kelompok: event itu pernah dikirim ke suatu alamat, atau tidak pernah dikirim ke mana pun.

Kalau tidak pernah dikirim, tidak ada yang salah dengan handler dan membacanya sepanjang apa pun tidak akan menemukan apa-apa. Log pengiriman di dashboard, pada menu Developer, menjawab pertanyaan itu dalam satu kata per event.

Satu keadaan berada di luar bahasan ini: integrasi yang masih berjalan di komputer sendiri dan belum pernah menerima satu pun pengiriman. Penyebabnya bukan pengiriman yang gagal melainkan alamat yang memang tidak pernah bisa dipakai, dan urutan menyiapkannya ada di artikel menguji webhook dari komputer sendiri.

Lima jawaban, dan penyebabnya masing-masing

Kolom status pada log menampilkan lima kemungkinan, dan masing-masing menunjuk ke arah yang berbeda. Membaca yang benar menghemat sebagian besar waktu penelusuran.

StatusArtinyaYang harus diperiksa
TerkirimEndpoint menjawab 2xxMasalahnya di dalam handler, bukan pada pengiriman
Mencoba ulangDitolak, tetapi jatah percobaan masih adaKode jawaban pada percobaan terakhir
Gagal permanenSemua percobaan habis ditolakPerbaiki endpoint, lalu kirim ulang
Tidak pernah dikirimTidak ada URL endpoint saat event menungguSimpan URL endpoint, lalu kirim ulang
Dijeda oleh kamiPengiriman ditahan selama akun ditinjauTidak ada, pengiriman lanjut sendiri

Saringan status di log juga menyediakan pilihan Ditolak, yang mencakup dua status sekaligus: yang ditolak dan masih mencoba, serta yang ditolak sampai jatahnya habis. Itu saringan yang tepat untuk pertanyaan “apa saja yang pernah ditolak endpoint ini”, terlepas dari apakah masih ada harapan atau tidak.

Tidak pernah dikirim: status yang paling sering salah dibaca

Ini penyebab yang paling banyak membuang waktu, karena tampilannya di sisi toko persis sama dengan endpoint yang bermasalah: pesanan tidak berubah, dan tidak ada apa pun di log server sendiri. Bedanya, di sisi kami event itu tidak pernah diposkan ke mana pun, jadi tidak ada yang menolaknya dan jumlah percobaannya masih nol.

Ada tiga cara sampai ke sana:

Yang penting diketahui: ketiganya menahan event, bukan menggagalkannya, dan tidak membakar satu pun percobaan ulang. Menyimpan URL atau mengaktifkan kembali endpoint membangunkan event yang tertahan, sehingga riwayat terbaru tetap terkirim tanpa perlu meminta apa pun. Itulah alasan menonaktifkan endpoint adalah cara yang benar untuk menghadapi pemeliharaan terjadwal, dan membiarkan endpoint menjawab 500 adalah cara yang salah.

Penahanan itu ada batasnya. Event yang tidak punya tujuan ditahan sampai tujuh hari sejak dibuat, lalu ditutup tanpa pernah terkirim. Satu minggu cukup untuk memasang handler; setelah itu event tersebut memang sudah basi.

Tangga percobaan ulang, dan cara menyetel peringatan sendiri

Kalau endpoint menjawab apa pun selain 2xx, atau tidak bisa dihubungi sama sekali, event masuk ke tangga percobaan ulang. Percobaan pertama berjalan seketika, lalu tiap kegagalan menjadwalkan yang berikutnya dengan jarak yang melebar: satu menit, lima menit, tiga puluh menit, dua jam, enam jam, dan dua puluh empat jam. Totalnya tujuh percobaan yang terentang sekitar 33 jam. Kebijakan ini juga tercetak di kartu webhook pada dashboard, dengan angkanya diambil langsung dari sistem, dan diringkas di dokumentasi webhook.

Rentang itu dipilih supaya deploy yang gagal pada Jumat sore masih tertolong pada Sabtu malam. Konsekuensi praktisnya ada dua. Pertama, endpoint yang mati kurang dari satu jam hampir selalu sembuh sendiri tanpa ada yang perlu dikerjakan. Kedua, peringatan internal sebaiknya disetel jauh lebih rapat daripada 33 jam, karena menunggu jatahnya habis berarti baru menyadari masalah pada hari berikutnya. Selisih antara percobaan ketiga dan keempat, yaitu tiga puluh menit menjadi dua jam, adalah tempat yang masuk akal untuk memasang ambang peringatan sendiri.

Setelah percobaan terakhir habis, event berhenti dan hanya tombol kirim ulang yang menghidupkannya lagi. Kirim ulang memberi tepat satu percobaan tambahan, langsung saat itu juga, jadi urutannya selalu sama: perbaiki endpoint dulu, baru kirim ulang. Kirim ulang pada endpoint yang belum diperbaiki hanya menghabiskan satu percobaan lagi.

Jumlah percobaan yang tersisa saat gangguannya ditemukan bergantung pada berapa lama endpoint mati, dan tangga itu jauh lebih berat di depan daripada yang terbaca dari angka 33 jam. Hitungannya untuk gangguan yang lewat tengah malam, beserta cara menahan event tanpa membakar percobaan saat pemeliharaan memang direncanakan, ada di artikel tentang server yang mati semalam.

Yang dihitung sebagai penolakan

Beberapa penyebab kegagalan tidak pernah sampai ke kode handler, dan mencarinya di sana sia-sia. Semuanya tercatat apa adanya pada baris percobaan, yang bisa dibuka satu per satu di log lengkap dengan nomor percobaan, kode HTTP atau pesan galatnya, dan durasinya.

Menjawab 2xx pada payload yang gagal diproses adalah kesalahan yang berlawanan arah, dan konsekuensinya dibahas di artikel tentang webhook pembayaran yang aman: jawaban 2xx mengakhiri seluruh tangga percobaan, sehingga notifikasi terakhir yang akan pernah datang dibuang begitu saja.

Endpoint yang benar, mode yang salah

Satu penyebab “webhook tidak sampai” sama sekali tidak berhubungan dengan pengiriman: pengujian dilakukan di satu mode sementara endpoint dipasang di mode lain. Mode live dan mode sandbox adalah endpoint terpisah dengan signing secret masing-masing. Event sandbox hanya dikirim ke endpoint sandbox dan membawa livemode: false; event live hanya dikirim ke endpoint live. Secret sandbox tidak akan pernah bisa memverifikasi payload live, dan sebaliknya.

Gejalanya khas: pembayaran sandbox terlihat berhasil, log pengiriman kosong pada mode itu, dan handler sama sekali tidak tersentuh. Periksa mode yang sedang aktif di dashboard, lalu pastikan endpoint pada mode tersebut memang punya URL. Cara mengelola kedua secret itu tanpa tertukar dibahas di artikel tentang menyimpan dan merotasi kunci API.

Kalau ada lebih dari satu endpoint, misalnya satu untuk toko dan satu untuk sistem pencatatan, tiap endpoint menerima salinannya sendiri, ditandatangani dengan secret-nya sendiri, dan mencoba ulang sendiri-sendiri. Satu endpoint yang mati tidak pernah menunda endpoint yang lain, dan status di log dibaca per endpoint. Maksimalnya lima endpoint per mode. Pilihan antara bentuk itu dan satu endpoint yang meneruskan sendiri ke sistem kedua dibandingkan di perbandingan satu endpoint webhook dan beberapa endpoint.

Urutan memeriksa

  1. Buka log pengiriman pada mode yang sesuai, lalu cari eventnya. Kotak pencarian menerima id event, id pembayaran, atau merchant_ref.
  2. Baca statusnya. Kalau Terkirim, endpoint sudah menjawab 2xx dan penelusuran berpindah ke log aplikasi sendiri. Kalau Tidak pernah dikirim, perbaiki endpointnya dan berhenti membaca kode.
  3. Buka daftar percobaannya. Kode HTTP atau pesan galat pada tiap baris menunjuk penyebabnya langsung, dan pola waktunya menunjukkan kapan endpoint mulai bermasalah.
  4. Lihat panel kesehatan endpoint. Persentase keberhasilan dan grafik terkirim dibanding gagal per hari memisahkan satu event yang aneh dari endpoint yang memang sedang jatuh.
  5. Setelah perbaikan, gunakan tombol kirim event percobaan untuk memastikan jalurnya hidup, baru kirim ulang event yang tertinggal.

Satu hal yang tidak perlu dipantau sendiri: kalau endpoint berhenti menjawab, kami mengirim satu pemberitahuan lewat surel untuk seluruh gangguan itu, bukan satu surel per event, ditutup dengan pemberitahuan pemulihan saat pengiriman berhasil lagi.

Yang memang tidak akan pernah datang

Terakhir, sebagian laporan “webhook tidak sampai” sebenarnya menunggu event yang tidak dikirim ke endpoint itu. Event pembayaran ada tiga, yaitu payment.paid, payment.expired, dan payment.failed, dan setiap endpoint hanya menerima yang dicentangnya. Endpoint yang dibuat sebelum pilihan event tersedia hanya menerima payment.paid sampai dua lainnya dicentang di dashboard. Pembatalan dan pengembalian dana tidak pernah dikirim sebagai event, sehingga pesanan yang perlu dilepas saat itu dilepas dari sisi toko sendiri, dengan expires_at sebagai cadangan untuk kedaluwarsa. Langkah memasang jalur itu dari awal ada di panduan integrasi QRIS di website, dan arti tiap kode penolakan ada di daftar kode galat.

Kalau kesimpulannya justru bahwa jalur pemberitahuan ini perlu ditemani pembacaan status dari sisi sendiri, pembagian peran keduanya beserta hitungan anggaran permintaannya ada di perbandingan webhook dan pembacaan status berkala.

Mulai sekarang

Terima pembayaran pertama minggu ini.

Daftar gratis, buat payment link atau pakai API, dan lihat statusnya berubah otomatis. Tanpa biaya bulanan, biaya transaksi hanya untuk pembayaran yang berhasil.

Dapatkan di Google PlayDownload di App Store