Setiap artikel webhook lain, termasuk artikel kami yang lain, berangkat dari asumsi yang sama: provider mengirim event dengan segera, lalu ada yang salah sesudahnya. WooCommerce mematahkan asumsi itu bahkan sebelum permintaan meninggalkan server.
Event tidak dikirim saat pesanan dibuat. Event dimasukkan ke antrean, dan antrean itu dikosongkan oleh cron WordPress, dan cron WordPress bukanlah cron. Cron itu berjalan saat seseorang memuat halaman.
Jika toko Anda ramai, keterlambatannya hingga sekitar satu menit dan Anda tidak akan pernah menyadarinya. Jika toko Anda kedatangan beberapa pengunjung per jam, notifikasi pesanan “real-time” tiba kapan pun pengunjung berikutnya kebetulan muncul.
TL;DR
- Pengiriman diantrekan sebagai action
woocommerce_deliver_webhook_asyncdi Action Scheduler - Action Scheduler digerakkan oleh WP-Cron, yang terpicu oleh pemuatan halaman, bukan oleh jadwal
- Karena itu, pengiriman webhook di toko bertrafik rendah lambat dan tidak bisa diprediksi, dan tidak ada yang rusak di situ
- “WooCommerce automatically disables a webhook after more than five consecutive delivery failures”
- Kegagalan adalah respons apa pun selain
2xx,301, atau302 - Solusi sebenarnya di WordPress self-hosted adalah menonaktifkan WP-Cron dan menjalankannya dari cron sistem
Mulai dengan memeriksa apakah event dikirim sama sekali
Sebelum men-debug endpoint Anda, pastikan dulu mana dari dua masalah yang sangat berbeda ini yang Anda hadapi: pengirimannya gagal, atau pengirimannya belum terjadi.
Menu WooCommerce → Settings → Advanced → Webhooks menampilkan status setiap webhook, dan WooCommerce → Status → Logs (sumber webhooks-delivery) menampilkan pengiriman terbarunya beserta kode respons. Jika pengiriman tercantum dan gagal, lompat ke sisa daftar periksa di bawah. Jika log kosong padahal pesanannya jelas ada, pengirimannya masih menunggu di antrean.
Untuk melihat antreannya sendiri, buka layar admin Action Scheduler di Tools → Scheduled Actions dan filter woocommerce_deliver_webhook_async. Action yang masih menunggu dengan stempel waktu (timestamp) di masa lalu adalah bukti kuncinya: action itu sudah jatuh tempo, dan tidak ada yang menjalankannya.
Mengapa WP-Cron berperilaku seperti ini
WordPress tidak punya proses latar belakang. Yang disebutnya cron adalah daftar tugas jatuh tempo yang diperiksa setiap ada permintaan masuk, jadi jamnya hanya maju saat ada trafik. Ini desain yang masuk akal untuk shared hosting pada 2005, dan desain yang buruk untuk apa pun yang sensitif terhadap waktu.
Akibatnya khusus untuk webhook:
Latensi pengiriman mengikuti trafik Anda, bukan event-nya. Toko dengan trafik stabil mengirim dalam sekitar satu menit. Toko B2B yang pesanannya masuk semalaman dan tidak ada yang membuka situs sampai pagi bisa membiarkan pengiriman yang sudah diantrekan menganggur selama berjam-jam.
Tumpukan antrean makin parah. Action yang gagal tetap tinggal di tabel, dan di toko yang sudah lama berjalan, tabel Action Scheduler tumbuh cukup besar untuk memperlambat permintaan yang justru seharusnya mengosongkannya.
Pekerjaan yang dipicu pemuatan halaman tidak andal jika ada caching. Full-page cache atau CDN di depan WordPress membuat banyak kunjungan tidak pernah sampai ke PHP sama sekali, artinya kunjungan itu tidak pernah memajukan cron.
Solusinya adalah operasi WordPress standar, dan layak dilakukan di toko mana pun yang webhook-nya penting:
// wp-config.php
define('DISABLE_WP_CRON', true);
# then drive it from a real scheduler, every minute
* * * * * cd /var/www/store && wp cron event run --due-now >/dev/null 2>&1
wp action-scheduler run adalah padanannya untuk mengosongkan antrean Action Scheduler secara langsung, jika paket WP-CLI tersedia. Begitu cron berjalan dengan timer sungguhan, latensi pengiriman tidak lagi bergantung pada ada tidaknya orang yang membuka situs.
Lebih dari lima kegagalan dan webhook-nya hilang
Inilah bagian yang mengubah masalah sementara menjadi masalah permanen.
“WooCommerce automatically disables a webhook after more than five consecutive delivery failures.” Definisi kegagalannya luas: “any response that is not a 2xx, 301, or 302 HTTP status code. For example, 4xx or 5xx responses count as failures.”
Itu angka yang kecil. Deploy yang mematikan endpoint Anda selama dua menit di toko dengan arus pesanan yang stabil akan dengan mudah menghabiskan pengiriman sebanyak itu. Saat itu terjadi, status webhook berubah menjadi nonaktif dan tetap begitu. Tidak ada email, tidak ada pemberitahuan di admin, tidak ada apa pun yang muncul di depan manusia. Pesanan terus dibuat dan tidak ada yang dikirim, dan itu terlihat persis seperti hari penjualan yang sepi.
Semua ini tidak khas WooCommerce. Webhook n8n Tidak Berfungsi mengurutkan tujuh penyebab di sisi otomasi berdasarkan seberapa sering masing-masing ternyata menjadi biangnya, dan antrean yang dikosongkan setidak merata ini dengan sendirinya mengirim secara tidak berurutan, topik yang dibahas di HubSpot Memberi Tahu Anda eventId-nya Mungkin Tidak Unik. Saat percobaan ulang (retry) benar-benar mendarat dua kali, Double Booking menunjukkan seperti apa itu di mata orang yang memesan.
Ini bentuk kegagalan yang sama dengan Shopify yang menghapus langganan, dengan dua perbedaan yang membuatnya lebih buruk: ambangnya lebih dari lima kegagalan berturut-turut, bukan delapan seperti di Shopify, dan sama sekali tidak ada email peringatan.
Jika Anda menjalankan WooCommerce di production, pasang pemeriksaan pada status webhook. Tugas terjadwal yang membaca webhook lewat REST API dan memberi peringatan jika status !== 'active' hampir tidak memakan biaya, dan hanya itulah yang akan memberi tahu Anda.
Verifikasi tanda tangan (signature)
WooCommerce mengirim X-WC-Webhook-Signature, yaitu HMAC-SHA256 dari raw body permintaan dalam encoding base64, yang dihitung dengan secret yang Anda konfigurasi pada webhook.
Base64, bukan hex: konvensi yang sama dengan Shopify dan kebalikan dari Stripe dan GitHub. Encoding yang dipilih tiap provider ada di referensi provider webhook. Jika Anda memakai satu helper verifikasi bersama untuk banyak provider, perbedaan encoding itu sering menjadi penyebab “tanda tangan tidak pernah cocok” pada payload yang terlihat sempurna.
import crypto from "node:crypto";
function verifyWooCommerce(raw: string, header: string | null, secret: string) {
if (!header) return false;
const expected = crypto.createHmac("sha256", secret).update(raw, "utf8").digest("base64");
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Seperti di mana pun, hitung hash dari byte yang Anda terima, bukan dari objek yang diserialisasi ulang. Perbandingan per provider ada di Verifikasi Tanda Tangan Webhook Stripe, GitHub, dan Shopify.
Satu jebakan khas WooCommerce: jika webhook dibuat tanpa secret, tanda tangannya dibuat dari material kunci milik situs itu sendiri, bukan dari sesuatu yang Anda pilih, dan verifikasi Anda akan gagal karena alasan yang tidak ada hubungannya dengan kode Anda. Tetapkan secret secara eksplisit.
Sisa daftar periksa
Telusuri poin-poin ini jika pengiriman ada dan gagal.
Status bernilai active, bukan paused atau disabled. Periksa ini lebih dulu, karena status nonaktif langsung menjelaskan semua hal lainnya.
Topiknya cocok dengan event yang Anda kira. order.created terpicu saat pesanan dibuat, yang pada banyak payment gateway terjadi saat pesanan masih pending. Jika Anda menunggu pembayaran, biasanya yang Anda perlukan adalah order.updated dengan pemeriksaan status. Banyak laporan “webhook tidak jalan” sebenarnya adalah “ternyata jalan untuk event lain dari yang diharapkan”.
URL pengiriman bisa dijangkau dari server, bukan hanya dari laptop Anda. Toko di balik jaringan korporat atau di host dengan aturan egress yang ketat bisa gagal di sini tanpa suara.
Ada plugin yang memotong jalannya action. Hook WooCommerce bisa difilter, dan plugin caching, keamanan, atau performa diketahui pernah mengganggu Action Scheduler. Menguji dengan plugin dinonaktifkan memang tidak glamor, tetapi sering menentukan.
Versi API webhook. Webhook WooCommerce punya payload berversi, dan webhook v1 yang dibuat bertahun-tahun lalu mengirim bentuk yang berbeda dari v3. Jika handler Anda mulai gagal setelah pembaruan WooCommerce, periksa versi yang dipakai webhook tersebut.
Peran AnyHook
Dua masalah di atas bersifat struktural, dan keduanya tidak bisa diperbaiki dengan handler yang lebih baik.
Yang pertama adalah penonaktifan karena kegagalan berturut-turut. Arahkan webhook ke in.anyhook.net/you/woo dan AnyHook mengembalikan 200 dalam sekitar satu detik dari edge, sehingga penghitung kegagalan berturut-turut tidak lagi bergantung pada apa yang sedang terjadi pada endpoint Anda sendiri. Percobaan ulang ke endpoint Anda berjalan dengan jadwal AnyHook, bukan jadwal WooCommerce, dan webhook tetap aktif.
Yang kedua: log pengiriman WooCommerce sendiri tinggal di dalam toko, mengikuti masa simpan toko, di layar admin. AnyHook menyimpan setiap event lengkap dengan header dan body, dengan enkripsi saat disimpan (at rest), dan putar ulang (replay) untuk satu rentang waktu, sehingga event selama jendela deploy bisa dipulihkan tanpa menyentuh WordPress.
- Peringatan kegagalan pada 1, 5, dan 20 kegagalan berturut-turut, notifikasi yang tidak dikirim WooCommerce
- Pengiriman dicoba ulang dengan backoff eksponensial, selama jauh lebih lama daripada satu upaya tunggal
- Satu header
AnyHook-Signaturesebagai pengganti varian base64, jika Anda menerima dari beberapa platform
Yang tidak bisa diperbaiki relay adalah antrean di depannya. Jika WP-Cron tidak berjalan, event tidak pernah sampai ke mana pun. Perbaiki cron lebih dulu; relay menangani semua hal setelah itu.
Kesimpulan
WooCommerce adalah satu-satunya platform besar tempat “webhook-nya terlambat” biasanya bukan bug dan bukan masalah endpoint Anda. Periksa antrean Action Scheduler sebelum memeriksa apa pun, dan jika trafik toko rendah, pindahkan cron ke scheduler sungguhan.
Lalu tetapkan secret, verifikasi terhadap raw byte dengan base64, dan pasang monitor pada status webhook itu sendiri. Segelintir kegagalan berturut-turut bukan ruang yang lega, dan penonaktifannya datang tanpa sepatah kata pun.