Kontrak API Idempoten di Next.js API Routes untuk Webhook perlu memastikan setiap event pihak ketiga hanya diproses sekali meskipun webhook digagal repeats atau diterima ulang oleh provider. untuk itu, pastikan route menerima data minimal yang memungkinkan server mengenali event unik, memverifikasi signature, menyimpan status pemrosesan, lalu memberi respons deterministik sehingga client bisa mengandalkan idempotensi.
Menjawab kebutuhan Kontrak API Idempoten di Next.js API Routes untuk Webhook
Idempoten berarti permintaan yang sama mengembalikan hasil identik tanpa efek samping kedua. Dalam konteks webhook, perancang kontrak harus menyepakati header, body, dan behavior respons yang menjamin satu event tidak diproses ulang secara tidak sengaja.
Komponen utama kontrak yang praktis mencakup:
- Header signature yang dapat diverifikasi dengan secret bersama.
- Identifikasi event unik seperti
event_idatau hash payload. - Metadata waktu untuk debugging dan tracing.
- Respons deterministik (misalnya HTTP 200 + body sederhana) untuk menyampaikan bahwa event sudah ditangani.
Dengan menyajikan hal-hal tersebut secara eksplisit, provider dan konsumen (Next.js) memiliki landasan bersama untuk idempoten tanpa asumsi tersembunyi.
Menentukan data minimal dan format payload
Data minimal yang harus diterima client untuk menjaga idempotensi adalah:
event_id: string unik yang disediakan penyedia webhook. Ini adalah kunci pertama dalam dedup.event_type: memetakan logika handler.timestamp(opsional tapi membantu audit).payloadyang bisa diserialisasi ke JSON untuk hash.- Signature header (contoh
X-SignatureatauX-Hub-Signature) untuk verifikasi integritas.
Jika data payload raw besar, kontrak bisa mengharuskan penyedia menyertakan hash SHA256/MD5 yang juga diverifikasi di sisi server untuk memastikan konten tidak berubah saat transit.
Verifikasi signature dan opsi validasi otentikasi
Verifikasi signature menggunakan secret bersama (shared secret) memastikan payload hanya diproses jika berasal dari sumber terpercaya. Pendekatan umum di Next.js App Router:
import { createHmac } from 'crypto';
function verifySignature(body: string, signatureHeader: string, secret: string) {
const computed = createHmac('sha256', secret).update(body).digest('hex');
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signatureHeader));
}
Letakkan logika ini sedekat mungkin dengan permintaan masuk agar payload tidak dimodifikasi sebelum verifikasi.
Middleware cocok untuk menolak request yang tidak sah sebelum masuk ke handler, sehingga request yang gagal tidak membebani handler atau database. Middleware juga ideal untuk header auth yang berlaku di beberapa route.
Handler memberikan konteks lengkap (body, database, dedup) yang memungkinkan validasi dilakukan setelah payload dikompilasi, misalnya ketika signature membutuhkan hash dari body serialisasi. Handler juga memungkinkan respons lebih detail.
Strategi terbaik: cek header dasar di middleware (misalnya signature ada dan format benar) lalu lakukan verifikasi penuh di handler sebelum memicu pemrosesan bisnis.
Menangani duplikat dan retry aman
Untuk memastikan idempoten, simpan status event di dedup table atau hash registry.
Contoh struktur tabel:
CREATE TABLE webhook_events (
event_id TEXT PRIMARY KEY,
payload_hash TEXT NOT NULL,
processed_at TIMESTAMPTZ NOT NULL,
status TEXT NOT NULL
);
Alur dedup sederhana:
- Buat hash payload (misalnya SHA256) dan bandingkan dengan record
event_idyang ada. - Jika
event_idbelum ada, sisipkan record dan proses event. - Jika ada dan hash cocok, lewati pemrosesan kedua.
- Jika hash berbeda, log dan kembalikan error karena indikasi data tidak konsisten.
Pola retry aman:
- Kirimkan HTTP 200/202 saat event berhasil diproses; webhook provider biasanya berhenti retry.
- Gunakan status 429/5xx dengan header
Retry-Afteruntuk memberi tahu provider kapan mencoba lagi. - Implementasikan backoff di sisi provider (umumnya sudah default), tetapi pelajari dokumentasi provider untuk menghindari flood.
Penting mencatat: idempoten di sisi server membuat handler tidak bergantung pada jumlah retry—kecuali ketika reward/perubahan apa pun harus dielaborasi ke luar (dedicated webhook queue, dsb.).
Contoh Route Handler Next.js App Router
import { NextResponse } from 'next/server';
import { createHmac } from 'crypto';
import { headers } from 'next/headers';
const SECRET = process.env.WEBHOOK_SECRET;
async function hasProcessed(eventId: string) {
// query dedup table, return true jika sudah ada
}
async function markProcessed(eventId: string, hash: string) {
// insert ke tabel dengan transaction
}
export async function POST(request: Request) {
const signature = headers().get('x-signature');
if (!signature) {
return NextResponse.json({ error: 'Missing signature' }, { status: 400 });
}
const rawBody = await request.text();
const computed = createHmac('sha256', SECRET).update(rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature))) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const payload = JSON.parse(rawBody);
const { event_id, event_type } = payload;
if (!event_id || !event_type) {
return NextResponse.json({ error: 'Missing event metadata' }, { status: 422 });
}
const payloadHash = createHmac('sha256', SECRET).update(rawBody).digest('hex');
if (await hasProcessed(event_id)) {
// respons tetap 200 agar provider berhenti retry
return NextResponse.json({ status: 'duplicate', event_id });
}
// Logika bisnis di sini, diikuti markProcessed
await markProcessed(event_id, payloadHash);
// ... proses event_type sesuai kontrak
return NextResponse.json({ status: 'ok', event_id });
}
Penting membungkus operasi dedup dan pemrosesan dalam transaksi database untuk menghindari race condition. Jika Anda menggunakan ORM seperti Prisma atau query builder, pastikan transaksi memegang kunci pada event_id sebelum memproses.
Strategi pengujian integrasi dan mock webhook
Pengujian integrasi memastikan seluruh kontrak bekerja.
- Gunakan testing suite (Jest, Vitest) untuk memanggil route handler dengan HTTP client internal (misalnya
fetchvianext/server). - Buat mock server yang meniru webhook provider, lengkap dengan signature dan retry behavior.
- Tambahkan tes untuk kasus duplikat, signature tidak valid, serta kehilangan field
event_id.
Pada tahap lebih lanjut, gunakan tools seperti Mock Service Worker (MSW) atau nock agar environment pengujian bisa menyimulasikan retry/backoff dari provider.
Masing-masing tes harus memverifikasi:
- Kode respons sesuai kontrak.
- Dedup table tidak menyimpan lebih dari satu entry per
event_id. - Database mencatat status dan hash payload.
Dengan kombinasi validasi signature, dedup, retry policy, dan pengujian, Anda memastikan kontrak API idempoten untuk Next.js API Routes memenuhi ekspektasi reliability dan keamanan webhook.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!