Gejalanya luar biasa senyap. Pesanan berhenti muncul di aplikasi Anda. Tidak ada error di log, karena tidak ada apa pun yang sampai ke log. Tidak ada pengiriman gagal di admin Shopify, karena tidak ada lagi langganan yang pengirimannya bisa ditampilkan. Shopify sudah menghapusnya, dan kecuali email peringatannya sampai ke kotak masuk yang dibaca seseorang, sinyal pertama adalah merchant yang bertanya mengapa pesanan dua hari terakhirnya hilang.
Aturan webhook Shopify lebih ketat daripada kebanyakan provider, dan ketatnya terpusat pada satu angka.
TL;DR
- Lima detik. Itulah seluruh batas waktu respons Anda, per pengiriman
- Shopify mencoba ulang 8 kali dalam 4 jam, dan setelah 8 kegagalan berturut-turut, langganan yang dibuat lewat Admin API dihapus (yang dideklarasikan di
shopify.app.tomltidak) - Dari sudut pandang aplikasi Anda, penghapusan ini senyap. Tidak ada trafik dan tidak ada error, karena tidak ada lagi langganan yang tersisa
- Email peringatan dan email penghapusan dikirim ke email darurat developer (emergency developer email) di akun Partner Anda, yang biasanya alamat yang tidak dipantau siapa pun
- Pemulihan berarti membuat ulang langganan dan melakukan backfill dengan Admin API, karena Shopify tidak memutar ulang (replay)
Ke mana lima detik itu sebenarnya terpakai
GitHub memberi Anda sepuluh detik, Stripe sama sekali tidak memublikasikan angkanya, dan sisanya ada di referensi provider webhook kami. Shopify menunggu respons selama lima detik dan menganggap apa pun yang lebih lambat sebagai pengiriman gagal.
Lima pun bukan angka terendah. Slack memberi tiga detik, dan Slack ketat soal itu, sampai mengecoh siapa pun yang selama ini hanya pernah mengintegrasikan toko online. Twilio adalah pengecualian yang menunjukkan betapa arbitrernya angka-angka ini: Twilio menyerahkan kenopnya kepada Anda dan membiarkan Anda mengatur timeout sendiri, dan hampir tidak ada yang pernah memutarnya.
Lima detik terdengar banyak sampai Anda menuliskan apa yang sebenarnya terjadi di dalam handler pada umumnya:
| Langkah | Normal | Hari buruk |
|---|---|---|
| Cold start (serverless) | 200 ms | 2,5 detik |
| Verifikasi HMAC | 2 ms | 2 ms |
| Cari toko berdasarkan domain | 30 ms | 400 ms |
| Tulis pesanan + line item | 80 ms | 1,2 detik |
| Sinkronisasi ke ERP / 3PL | 600 ms | timeout |
| Kirim email konfirmasi | 250 ms | 3 detik |
Jalur normalnya 1,2 detik dan terlihat sepenuhnya sehat. Lalu ERP Anda lambat suatu pagi, setiap pengiriman melewati lima detik secara bersamaan, dan Anda menghabiskan kedelapan percobaan ulang (retry) dalam empat jam. Tidak ada yang berubah di kode Anda. Tidak ada yang salah di kode Anda. Hari Kamis, langganannya sudah hilang.
Dua faktor yang memperparah ini khas Shopify.
Flash sale membuat kegagalan terjadi bersamaan. Webhook datang sebanding dengan jumlah pesanan, jadi saat Anda paling membutuhkan orders/create adalah saat database Anda paling sibuk dan waktu respons Anda paling buruk. Mode kegagalannya berkorelasi terbalik persis dengan kapan Anda sanggup menanggungnya.
Operasi massal memicu webhook dalam jumlah besar sekaligus. Merchant yang mengedit 5.000 produk lewat aplikasi atau impor CSV menghasilkan 5.000 webhook products/update dalam waktu singkat. Endpoint Anda kini dibatasi oleh connection pool database Anda sendiri, dan setiap pengiriman yang lewat dari lima detik dihitung merugikan Anda.
Seperti apa penghapusan itu sebenarnya
Setelah delapan upaya gagal dalam jendela empat jam, Shopify berhenti mencoba ulang event itu, dan setelah 8 kegagalan berturut-turut, langganan yang dibuat lewat Admin API dihapus, dengan email peringatan ke kontak darurat developer aplikasi tersebut. Langganan khusus aplikasi yang dideklarasikan di shopify.app.toml tidak dihapus dengan cara ini.
Shopify memang mengirimi Anda email: peringatan saat kegagalan berulang terdeteksi, dan email kedua saat langganannya dihapus. Keduanya dikirim ke email darurat developer di akun Partner Anda. Dalam praktiknya, alamat itu diatur sekali saat onboarding, sering kali ke alamat pribadi seseorang yang sudah keluar, dan itulah alasan paling umum sebuah tim terkejut oleh kejadian ini.
Periksa alamat itu sekarang. Butuh tiga puluh detik, dan itu hal paling berharga di artikel ini.
Setelah dihapus, webhookSubscriptions tidak mengembalikan apa pun untuk topik itu, dan tidak ada pengiriman maupun kegagalan karena tidak ada lagi yang bisa gagal. Event yang muncul selama langganannya tidak ada juga tidak diantrekan di mana pun. Shopify tidak memutar ulang setelah langganan dibuat ulang.
Poin terakhir itulah yang membuat ini mahal. Berbeda dengan endpoint Stripe yang dinonaktifkan, yang event-nya masih ada di API selama 30 hari, langganan Shopify yang terhapus berarti webhook tersebut memang tidak pernah dikirim. Pemulihannya adalah pekerjaan rekonsiliasi terhadap Admin API, bukan putar ulang.
Platform self-hosted juga tidak lebih lunak: WooCommerce juga menonaktifkan webhook setelah lebih dari lima kegagalan berturut-turut.
Merespons di bawah lima detik
Satu-satunya pola yang andal: verifikasi, simpan, konfirmasi penerimaan, dan kerjakan pekerjaan sebenarnya di tempat lain. Pemeriksa timeout menunjukkan seberapa jauh handler Anda melewati lima detik saat ini, dan jika langkah yang lambat adalah pemanggilan model, Handler Webhook Anda Memanggil LLM memuat pola yang sama untuk setiap platform.
export async function POST(req: Request) {
const raw = await req.text(); // raw body, required for HMAC
if (!verifyShopifyHmac(raw, req.headers.get("x-shopify-hmac-sha256"))) {
return new Response("unauthorized", { status: 401 });
}
await queue.publish({ // durable write, ~10ms
topic: req.headers.get("x-shopify-topic"),
shop: req.headers.get("x-shopify-shop-domain"),
webhookId: req.headers.get("x-shopify-webhook-id"),
body: raw,
});
return new Response(null, { status: 200 }); // well inside 5s
}
Empat detail yang perlu dipahami dengan tepat.
Baca raw body, bukan JSON yang sudah di-parse. HMAC Shopify dihitung atas byte yang persis, jadi middleware apa pun yang mem-parse lalu menyerialisasi ulang akan mengubah urutan key atau spasi, dan setiap verifikasi tanda tangan (signature) akan gagal. Di Next.js App Router, panggil await req.text() sebelum parsing apa pun. Lihat Verifikasi Tanda Tangan Webhook Stripe, GitHub, dan Shopify untuk detail per provider.
Verifikasi sebelum memasukkan ke antrean, bukan sesudahnya. Verifikasi butuh sekitar 2 ms, dan itulah gerbang yang mencegah penyerang memenuhi antrean Anda. Gerbang yang murah lebih dulu.
Lakukan deduplikasi berdasarkan X-Shopify-Webhook-Id. Nilainya tetap sama di semua percobaan ulang untuk event yang sama, sehingga itulah idempotency key yang tepat. Shopify akan mengirim ulang, jadi consumer Anda harus idempoten. Polanya ada di Anda Menerima Webhook yang Sama Dua Kali.
Waspadai cold start. Di serverless, cold start bisa menghabiskan separuh batas waktu Anda sebelum kode Anda berjalan. Jaga agar graf dependensi route webhook tetap kecil: meng-import seluruh ORM dan SDK admin ke handler yang hanya perlu memasukkan byte ke antrean adalah biaya nyata yang bisa dihindari.
Memulihkan setelah penghapusan
Pertama, buat ulang langganannya.
mutation {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
callbackUrl: "https://your-app.example.com/webhooks/shopify"
format: JSON
}
) {
webhookSubscription { id }
userErrors { field message }
}
}
Kedua, rekonsiliasi celahnya dari Admin API. Karena tidak ada yang diputar ulang, lakukan query langsung ke objeknya untuk rentang waktu gangguan itu, lalu proses lewat jalur kode yang sama dengan yang dipakai consumer webhook Anda:
{
orders(first: 250, query: "updated_at:>2026-07-28T00:00:00Z") {
edges { node { id name updatedAt displayFinancialStatus } }
}
}
Jalankan hasilnya lewat consumer idempoten Anda. Yang sudah ditangani tidak berefek apa pun; yang terlewat akan diterapkan. Ini juga saatnya Anda tahu apakah consumer Anda benar-benar idempoten.
Ketiga, audit topik mana saja yang hilang. Tampilkan daftar langganan saat ini dan bandingkan dengan yang diharapkan aplikasi Anda. Penghapusan terjadi per langganan, jadi sangat wajar kehilangan orders/create sementara products/update selamat, dan itu membuat gejalanya makin membingungkan.
Keempat, perbaiki email darurat developer, lalu tambahkan monitoring Anda sendiri di atasnya. Peringatan “tidak ada orders/create yang diterima dalam 6 jam selama jam kerja” akan menangkap masalah ini di hari pertama.
Peran AnyHook
Batas waktu lima detik adalah masalah transport, dan transport persis yang digantikan oleh relay.
Arahkan langganan Shopify ke in.anyhook.net/you/shopify. AnyHook mengembalikan 200 dalam sekitar satu detik, entah ERP Anda sehat atau sedang terbakar. Jam Shopify kini berjalan terhadap jawaban itu, bukan terhadap server origin Anda, sehingga lambatnya server origin tidak lagi memulai hitung mundur delapan percobaan ulang. Satu-satunya pengecualian adalah event yang sama sekali tidak bisa disimpan AnyHook: dalam kasus itu AnyHook menjawab 503, bukan 200, dan percobaan ulang dari Shopify sendiri yang mengambil alih.
- Lambatnya server origin Anda terlepas dari jam Shopify. Kami mencoba ulang ke server Anda dengan jeda 5 menit, 30 menit, 3 jam, 18 jam, lalu setiap hari, 3 sampai 10 upaya tergantung paket. Di Free, upaya terakhir terjadi di menit ke-35, jadi gangguan yang lebih lama ditangani oleh log yang tersimpan dan putar ulang
- Setiap event disimpan sebelum pengiriman dicoba, dengan enkripsi saat disimpan (at rest), jadi celah bisa diputar ulang dari log kami, bukan direkonstruksi dari Admin API
- Peringatan kegagalan beruntun dikirim pada 1, 5, dan 20 kegagalan berturut-turut, ke alamat yang Anda kendalikan, dan pengiriman dijeda otomatis pada kegagalan ke-20
- Putar ulang sekali klik mencakup rentang waktu selama server Anda mati, dan memutar ulang event yang gagal tidak mengurangi kuota bulanan Anda, meskipun ada batas harian dan memakai jatah komputasi (compute allowance) Anda
Aplikasi publik yang dipakai banyak toko cukup memakai satu App AnyHook, bukan satu per toko: arahkan langganan setiap toko ke URL masuk (inbound URL) yang sama. Shopify menandatangani webhook aplikasi dengan client secret aplikasi Anda, yang sama untuk setiap toko, jadi itulah secret yang diperiksa AnyHook, dan header X-Shopify-Shop-Domain tetap memberi tahu handler Anda toko mana yang mengirim setiap event.
Aturan lima detik Shopify adalah pernyataan tentang endpoint yang diajaknya bicara. Taruh di sana endpoint yang menjawab dalam sekitar satu detik, dan aturan itu berhenti membatasi logika bisnis Anda.
Kesimpulan
Lima detik dari Shopify bukan target yang longgar, dan hukuman karena berulang kali melewatinya adalah penghapusan, bukan endpoint yang dijeda dan bisa Anda lihat di dashboard. Pangkas jalur respons menjadi verifikasi, simpan, konfirmasi penerimaan, lakukan deduplikasi berdasarkan X-Shopify-Webhook-Id, lalu periksa alamat yang dituju email darurat developer di akun Partner Anda. Ke sanalah satu-satunya peringatan yang Anda dapat akan dikirim.