Menangani Edge Case Retry Webhook Third-Party di API Route Next.js berarti memastikan rute API mampu menolak payload yang tidak sah, mengidentifikasi duplikat, dan memberikan respons konsisten sehingga endpoint tidak diekspos pada eksekusi ganda akibat retry otomatis pihak ketiga. Fokusnya adalah pada validasi kontrak payload, pemeriksaan tanda tangan, idempotensi, dan observabilitas agar integrasi tetap stabil meskipun terjadi retry terbatas.
Artikel ini membahas pendekatan praktis dengan middleware sederhana, pola penyimpanan state untuk idempotensi, status code yang sesuai, serta rekomendasi batas retry yang menjaga konsistensi data tanpa mengganggu alur pengiriman webhook.
Memahami kontrak payload dan validasi tanda tangan
Langkah pertama adalah mendefinisikan kontrak payload: fields yang wajib ada (misalnya id, type, data) dan format data. Pihak ketiga harus menyertakan header signature yang dihasilkan dari payload yang sama, sehingga kita bisa menjamin payload tidak dimodifikasi atau replay dari pihak ketiga lain.
Validasi dilakukan dengan menghitung HMAC payload menggunakan secret bersama, lalu membandingkan dengan header signature. Pastikan payload diambil sebagai string persis seperti saat pihak ketiga menghitung signature (biasanya raw body).
import { createHmac } from 'crypto';
const verifySignature = (rawBody, signature, secret) => {
const computed = createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return computed === signature;
};
Selain itu, selalu cek skema payload: jika id tidak ada atau type tidak dikenali, hentikan pemrosesan dengan status 400 Bad Request dan log alasan untuk debugging.
Membangun middleware verifikasi dan idempotensi
Middleware untuk verifikasi
Di API Route Next.js, gunakan handler tunggal yang memanggil helper verifikasi sebelum menjalankan logika bisnis. Struktur sederhana bisa seperti ini:
export default async function handler(req, res) {
const rawBody = await readRawBody(req); // helper yang menangkap body mentah
const signature = req.headers['x-signature'];
if (!verifySignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Signature mismatch' });
}
const payload = JSON.parse(rawBody);
if (!isValidPayload(payload)) {
return res.status(400).json({ error: 'Invalid payload' });
}
// lanjut ke idempotensi dan pemrosesan
}
Jangan lupa mencatat setiap kegagalan validasi ke sistem logging/observability agar bisa dianalisis jika pihak ketiga terus mengirim payload buruk.
Idempotensi dan deteksi duplikat
Webhook retry sering mengirim ulang payload dengan id yang sama. Terapkan idempotensi dengan menyimpan id event di cache atau database dengan TTL pendek (misalnya 5-10 menit). Jika event sudah diproses, respons dengan 409 Conflict atau 204 No Content tergantung apakah client harus tahu bahwa event diabaikan.
const dedupKey = `webhook:${payload.id}`;
if (await cache.exists(dedupKey)) {
logInfo('webhook.retry.duplicate', { eventId: payload.id });
return res.status(409).json({ status: 'duplicate' });
}
await cache.set(dedupKey, 'processed', { ttl: 60 * 10 });
Gunakan store seperti Redis atau database transactional yang mendukung upsert. Transaksi membantu menghindari race condition saat beberapa retry tiba hampir bersamaan.
Jika event hanya perlu diproses sekali, buat handler yang idempoten secara internal (misalnya upsert ke database dengan unique constraint) dan laporkan status yang sama setelah duplikat terdeteksi.
Status code dan batas retry yang sesuai
Saat webhook pihak ketiga melakukan retry, status code menentukan apakah retry berhenti atau dilanjutkan:
- 200 OK atau 204 No Content: event berhasil/diabaikan – pihak ketiga umumnya berhenti mencoba.
- 400 Bad Request atau 401 Unauthorized: permintaan tidak valid – pihak ketiga perlu memperbaiki payload atau signature sebelum mengirim ulang.
- 409 Conflict: duplikat idevent – pemberitahuan bahwa event sudah diproses.
- 500 Internal Server Error atau 503 Service Unavailable: masalah server sementara; pihak ketiga biasanya akan retry otomatis.
Rekomendasi batas retry adalah antara 3 sampai 5 kali dengan backoff eksponensial. Batas ini mempertimbangkan bahwa webhook third-party biasanya menggunakan retry otomatis; terlalu banyak retry bisa menyebabkan beban duplikat, sementara terlalu sedikit bisa membuat event terlewat jika gangguan sementara terjadi.
Jika integrasi Anda mendukung pola acknowledge-then-queue, pertimbangkan mengembalikan 202 Accepted setelah menerima payload valid dan memprosesnya secara asynchronous untuk menghindari timeout client.
Observabilitas melalui logging dan metrics
Observabilitas mencegah masalah tersembunyi saat retry terjadi. Log setiap langkah penting:
- Validasi signature gagal (
WARN). - Payload valid tapi terjadi duplikat (
INFOdengan event ID). - Kesalahan jaringan atau database saat memproses event (
ERRORdengan stack trace).
Gunakan metrik untuk mengetahui frekuensi retry yang gagal atau jumlah duplikat dalam satu periode. Misalnya, ekspor meter webhook.received, webhook.duplicates, dan webhook.errors ke Prometheus atau tooling observability pilihan Anda.
Jika memungkinkan, sertakan trace ID dalam log untuk menghubungkan log level rendah (misalnya database) dengan log level tinggi (webhook handler). Ini mempermudah debugging ketika integrasi pihak ketiga mengalami masalah berulang.
Kesimpulan
Menangani edge case retry webhook third-party di API Route Next.js mensyaratkan kontrak payload yang jelas, verifikasi signature yang kuat, idempotensi, serta observabilitas yang menyeluruh. Dengan menambahkan middleware validasi, cache deduplikasi, status code yang tepat, dan pemantauan log/metrics, Anda menjaga integrasi tetap konsisten meski retry terjadi. Terapkan batas retry di sisi pihak ketiga (3-5 kali) dan pastikan handler mampu menanggapi setiap percobaan tanpa memicu efek samping yang tidak diinginkan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!