Kasus webhook sering kali melibatkan sistem pengirim yang melakukan retry saat tidak menerima respons sukses, sekaligus melemparkan signature atau token otentikasi yang harus diperiksa. Untuk menjawab tantangan ini, kita perlu membuat kontrak endpoint di SvelteKit yang memvalidasi payload, mengecek header auth, dan merespons secara idempoten sehingga retry tidak menimbulkan efek samping.
Artikel ini menunjukkan bagaimana menggabungkan schema validation, middleware signature/auth, penanganan retry-backoff, dan pengujian integrasi agar route webhook tetap tahan terhadap duplicate requests dan percobaan otentikasi yang jebol.
1. Memahami Kontrak Webhook yang Dibutuhkan
Kontrak webhook harus mencakup:
- Payload schema yang mendeskripsikan field wajib dan tipe data untuk mencegah crash saat parsing.
- Header auth atau signature yang membuktikan request berasal dari pengirim yang sah.
- Idempotensi supaya retry tidak menggandakan operasi.
Dalam SvelteKit, API route di bawah src/routes/api/webhook/+server.ts menjadi titik masuk. Kita harus membaca payload dengan request.json(), memverifikasi signature, dan mencatat eventId untuk mendeteksi duplicate.
2. Schema Payload dan Middleware Auth
Gunakan library schema validation seperti zod atau superstruct agar validasi dijalankan sebelum logika bisnis berlanjut. Definisikan schema dan buat helper untuk memvalidasi sekaligus mengeluarkan response 422 bila tidak sesuai.
import { json } from '@sveltejs/kit';
import { z } from 'zod';
const payloadSchema = z.object({
eventId: z.string(),
orderId: z.string(),
status: z.enum(['created', 'updated', 'cancelled']),
data: z.object({
amount: z.number(),
metadata: z.record(z.string())
})
});
const verifyPayload = async (request: Request) => {
const body = await request.json();
const result = payloadSchema.safeParse(body);
if (!result.success) {
throw json({ error: 'Payload tidak sesuai schema' }, { status: 422 });
}
return result.data;
};
Untuk header auth, buat middleware sederhana yang memeriksa signature HMAC atau token. Jangan letakkan logic validasi di handler utama agar mudah diuji.
const verifySignature = (request: Request, secret: string) => {
const signature = request.headers.get('x-signature');
if (!signature) {
throw json({ error: 'Header signature hilang' }, { status: 401 });
}
const raw = request.headers.get('x-payload-id');
const expected = createHmac('sha256', secret).update(raw || '').digest('hex');
if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw json({ error: 'Signature tidak valid' }, { status: 401 });
}
};
Kombinasikan kedua langkah tersebut di handler utama agar urutan validasi tetap konsisten.
3. Menangani Retry, Dedup, dan Idempotensi
Webhook sender sering retry dengan strategi backoff. Untuk menghindari efek samping, simpan eventId bersamaan dengan status proses di cache atau database. Jika eventId sudah diproses, segera balas 200 tanpa menduplikasi tindakan.
const processedEvents = new Map(); // diganti storage persistent
const ensureIdempotent = (eventId: string) => {
if (processedEvents.has(eventId)) {
return true; // sudah diproses
}
processedEvents.set(eventId, true);
setTimeout(() => processedEvents.delete(eventId), 1000 * 60 * 5); // expire
return false;
};
Untuk menjaga state saat retry terjadi di tengah proses, pertimbangkan transactional update (misal dengan database) yang menggabungkan eventId dan log status. Jika callback mengganggu database dengan partial commit, implementasikan compensating action atau rollback manual.
4. Rangkaian Handler SvelteKit
Gabungkan semua bagian di +server.ts:
import { json } from '@sveltejs/kit';
export const POST = async ({ request }) => {
const secret = process.env.WEBHOOK_SECRET;
if (!secret) {
return json({ error: 'Secret tidak dikonfigurasi' }, { status: 500 });
}
verifySignature(request, secret);
const payload = await verifyPayload(request);
if (ensureIdempotent(payload.eventId)) {
return json({ message: 'Event sudah diproses sebelumnya' });
}
await updateOrderStatus(payload.orderId, payload.status, payload.data);
return json({ message: 'Event diterima' });
};
Metode updateOrderStatus harus bersifat idempotent juga: misalnya, memperbarui state hanya jika status berbeda, dan mencatat timestamp terakhir.
5. Pengujian Integrasi untuk Konsistensi
Pengujian integrasi dapat dilakukan dengan memanggil route melalui HTTP client (misal fetch dalam playwright atau test runner). Fokuskan empat skenario:
- Payload valid + signature valid → status 200.
- Payload valid tapi signature salah → 401.
- Retry event dengan eventId sama → tidak menggandakan operasi, respon 200.
- Payload tidak cocok schema → 422.
Berikan intersepsi ke penyimpanan eventId agar Anda bisa memeriksa bahwa jumlah update ke database sesuai ekspektasi saat event duplikat terjadi. Gunakan mocking atau test database agar state dapat diperiksa setelah request.
Kesimpulan
Kemampuan SvelteKit menangani webhook yang tahan retry dan validasi auth bertumpu pada pemisahan tanggung jawab: middleware schema/signature sebelum pekerjaan utama, mekanisme deduplikasi event, serta pengujian integrasi yang memastikan konsistensi state. Pendekatan ini membuat endpoint lebih dapat diandalkan dalam lingkungan webhook riil yang sering melakukan retry otomatis.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!