Ringkasan
Rancang webhook yang andal dengan percobaan ulang backoff eksponensial (5-10 kali percobaan), kunci idempoten, verifikasi tanda tangan HMAC, dan batas waktu 5 detik. Segera kembalikan respons 2xx, proses secara asinkron. Modern PetstoreAPI mengimplementasikan webhook untuk pembaruan pesanan, adopsi hewan peliharaan, dan notifikasi pembayaran dengan fitur percobaan ulang dan keamanan penuh.
Pendahuluan
Anda mengirim webhook untuk memberi tahu klien bahwa hewan peliharaan mereka telah diadopsi. Server klien sedang mati. Webhook Anda gagal. Apakah Anda mencoba lagi? Berapa kali? Bagaimana jika klien menerima webhook dua kali dan menagih pelanggan dua kali?
Webhook adalah panggilan balik HTTP yang mendorong peristiwa ke URL klien. Secara teori sederhana, tetapi dalam praktiknya kompleks. Jaringan bisa gagal, server bisa crash, dan klien bisa memiliki bug. Webhook produksi memerlukan logika percobaan ulang, idempoten, keamanan, dan pemantauan.
Modern PetstoreAPI mengimplementasikan webhook yang siap produksi untuk pembaruan pesanan, adopsi hewan peliharaan, dan notifikasi pembayaran. Setiap webhook mencakup logika percobaan ulang, verifikasi tanda tangan, dan idempoten.
Dalam panduan ini, Anda akan mempelajari cara merancang webhook yang andal menggunakan pola Modern PetstoreAPI.
Dasar-dasar Webhook
Webhook adalah permintaan HTTP POST yang dikirim ke URL yang disediakan klien saat suatu peristiwa terjadi.
Bagaimana Webhook Bekerja
1. Klien mendaftarkan URL webhook:
POST /webhooks
{
"url": "https://client.com/webhooks/petstore",
"events": ["pet.adopted", "order.completed"]
}
2. Peristiwa terjadi (hewan peliharaan diadopsi)
3. Server mengirim webhook:
POST https://client.com/webhooks/petstore
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
{
"event": "pet.adopted",
"data": {
"petId": "019b4132",
"userId": "user-456",
"timestamp": "2026-03-13T10:30:00Z"
}
}
4. Klien merespons:
200 OK
Masalah Keandalan
Webhook bisa gagal karena banyak alasan:
- Server klien mati
- Batas waktu jaringan
- Klien mengembalikan kesalahan 500
- Klien lambat (membutuhkan 30 detik)
- Klien menerima webhook tetapi crash sebelum memprosesnya
Tanpa logika percobaan ulang, peristiwa akan hilang. Tanpa idempoten, webhook duplikat menyebabkan tindakan duplikat.
Logika Percobaan Ulang dengan Backoff Eksponensial
Coba lagi webhook yang gagal dengan penundaan yang meningkat.
Strategi Backoff Eksponensial
Percobaan 1: Segera
Percobaan 2: 1 detik kemudian
Percobaan 3: 2 detik kemudian
Percobaan 4: 4 detik kemudian
Percobaan 5: 8 detik kemudian
Percobaan 6: 16 detik kemudian
Mengapa eksponensial? Jika klien sedang mati, membanjirinya dengan percobaan ulang tidak akan membantu. Backoff eksponensial memberikan waktu untuk pemulihan.
Implementasi
async function sendWebhook(url, payload, attempt = 1, maxAttempts = 6) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': generateSignature(payload)
},
body: JSON.stringify(payload),
timeout: 5000 // batas waktu 5 detik
});
if (response.ok) {
return { success: true, attempt };
}
// Coba lagi pada kesalahan 5xx
if (response.status >= 500 && attempt < maxAttempts) {
const delay = Math.pow(2, attempt - 1) * 1000;
await sleep(delay);
return sendWebhook(url, payload, attempt + 1, maxAttempts);
}
// Jangan coba lagi pada kesalahan 4xx (kesalahan klien)
return { success: false, status: response.status };
} catch (error) {
// Kesalahan jaringan atau batas waktu - coba lagi
if (attempt < maxAttempts) {
const delay = Math.pow(2, attempt - 1) * 1000;
await sleep(delay);
return sendWebhook(url, payload, attempt + 1, maxAttempts);
}
return { success: false, error: error.message };
}
}
Kapan Harus Mencoba Lagi
Coba lagi pada:
- Kesalahan server 5xx (500, 502, 503, 504)
- Batas waktu jaringan
- Koneksi ditolak
- Kegagalan DNS
Jangan coba lagi pada:
- Kesalahan klien 4xx (400, 401, 404) - klien tidak akan memperbaikinya
- Sukses 2xx - sudah berhasil
Antrean Pesan Gagal (Dead Letter Queue)
Setelah percobaan ulang maksimum, pindahkan webhook yang gagal ke antrean pesan gagal untuk ditinjau secara manual:
if (!result.success) {
await deadLetterQueue.add({
url,
payload,
attempts: maxAttempts,
lastError: result.error,
timestamp: new Date()
});
}
Idempoten untuk Pencegahan Duplikasi
Klien mungkin menerima webhook yang sama berkali-kali. Idempoten mencegah pemrosesan duplikat.
Kunci Idempoten
Sertakan ID unik dengan setiap webhook:
{
"id": "webhook_019b4132",
"event": "pet.adopted",
"data": {...}
}
Klien menyimpan ID yang telah diproses:
app.post('/webhooks/petstore', async (req, res) => {
const webhookId = req.body.id;
// Periksa apakah sudah diproses
const processed = await db.webhooks.findOne({ id: webhookId });
if (processed) {
return res.status(200).json({ message: 'Already processed' });
}
// Proses webhook
await processPetAdoption(req.body.data);
// Tandai sebagai diproses
await db.webhooks.insert({ id: webhookId, processedAt: new Date() });
res.status(200).json({ message: 'Processed' });
});
Operasi Idempoten
Rancang operasi agar idempoten:
Buruk (tidak idempoten):
// Penagihan dua kali menyebabkan penagihan ganda
await chargeCustomer(userId, amount);
Baik (idempoten):
// Penagihan dengan kunci idempoten mencegah penagihan ganda
await chargeCustomer(userId, amount, { idempotencyKey: webhookId });
Verifikasi Tanda Tangan untuk Keamanan
Verifikasi webhook berasal dari API Anda, bukan dari penyerang.
Tanda Tangan HMAC
Buat tanda tangan menggunakan kunci rahasia bersama:
// Server menghasilkan tanda tangan
const crypto = require('crypto');
function generateSignature(payload, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex');
}
// Sertakan dalam header
headers['X-Webhook-Signature'] = `sha256=${generateSignature(payload, webhookSecret)}`;
Klien memverifikasi tanda tangan:
function verifySignature(payload, signature, secret) {
const expected = generateSignature(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
app.post('/webhooks/petstore', (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Proses webhook
...
});
Validasi Cap Waktu
Sertakan cap waktu untuk mencegah serangan replay:
{
"id": "webhook_019b4132",
"timestamp": "2026-03-13T10:30:00Z",
"event": "pet.adopted",
"data": {...}
}
Tolak webhook lama:
const webhookAge = Date.now() - new Date(req.body.timestamp);
if (webhookAge > 5 * 60 * 1000) { // 5 menit
return res.status(400).json({ error: 'Webhook too old' });
}
Penanganan Batas Waktu
Atur batas waktu yang agresif untuk mencegah klien yang lambat memblokir sistem Anda.
Batas Waktu 5 Detik
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(payload),
timeout: 5000 // 5 detik
});
Mengapa 5 detik? Webhook harus segera mengembalikan respons. Jika klien membutuhkan waktu lebih lama, mereka melakukan pemrosesan sinkron (pola yang salah).
Pola Pemrosesan Asinkron
Buruk (sinkron):
app.post('/webhooks/petstore', async (req, res) => {
// Ini membutuhkan 30 detik - webhook akan mencapai batas waktu
await processOrder(req.body.data);
await sendEmail(req.body.data);
await updateInventory(req.body.data);
res.status(200).json({ message: 'Processed' });
});
Baik (asinkron):
app.post('/webhooks/petstore', async (req, res) => {
// Segera kembalikan respons
res.status(200).json({ message: 'Received' });
// Proses secara asinkron
queue.add('process-webhook', req.body);
});
Bagaimana Modern PetstoreAPI Mengimplementasikan Webhook
Modern PetstoreAPI mengimplementasikan webhook yang siap produksi.
Peristiwa Webhook
pet.adopted - Hewan peliharaan diadopsi
pet.status_changed - Status hewan peliharaan berubah
order.created - Pesanan dibuat
order.completed - Pesanan selesai
payment.succeeded - Pembayaran berhasil
payment.failed - Pembayaran gagal
Payload Webhook
{
"id": "webhook_019b4132-70aa-764f-b315-e2803d882a24",
"event": "pet.adopted",
"timestamp": "2026-03-13T10:30:00Z",
"data": {
"petId": "019b4132-70aa-764f-b315-e2803d882a24",
"userId": "user-456",
"orderId": "order-789",
"adoptionDate": "2026-03-13"
},
"apiVersion": "v1"
}
Konfigurasi Percobaan Ulang
- Percobaan maksimum: 10
- Backoff: Eksponensial (1d, 2d, 4d, 8d, 16d, 32d, 64d, 128d, 256d, 512d)
- Jendela percobaan ulang total: ~17 menit
- Antrean pesan gagal setelah percobaan ulang maksimum
Keamanan
- Tanda tangan HMAC-SHA256 di header
X-Webhook-Signature - Validasi cap waktu (menolak yang lebih dari 5 menit)
- HTTPS wajib untuk URL webhook
Menguji Webhook dengan Apidog
Apidog mendukung pengujian webhook.
Menguji Pengiriman Webhook
- Buat mock endpoint webhook di Apidog
- Daftarkan endpoint dengan PetstoreAPI
- Picuan peristiwa (adopsi hewan peliharaan)
- Verifikasi webhook diterima
- Periksa format payload
Menguji Verifikasi Tanda Tangan
// Skrip pengujian Apidog
const signature = pm.request.headers.get('X-Webhook-Signature');
const payload = pm.request.body.raw;
const secret = pm.environment.get('WEBHOOK_SECRET');
const expected = generateSignature(payload, secret);
pm.test('Tanda tangan valid', () => {
pm.expect(signature).to.equal(`sha256=${expected}`);
});
Menguji Logika Percobaan Ulang
- Kembalikan kesalahan 500 dari mock endpoint
- Verifikasi upaya percobaan ulang dengan backoff eksponensial
- Periksa antrean pesan gagal setelah percobaan ulang maksimum
Menguji Idempoten
- Terima webhook
- Kembalikan 200
- Terima webhook yang sama lagi (percobaan ulang yang disimulasikan)
- Verifikasi tidak ada pemrosesan duplikat
Kesimpulan
Webhook yang andal memerlukan:
- Percobaan ulang backoff eksponensial (5-10 kali percobaan)
- Kunci idempoten untuk mencegah duplikat
- Verifikasi tanda tangan HMAC untuk keamanan
- Batas waktu 5 detik
- Pemrosesan asinkron di sisi klien
- Antrean pesan gagal untuk webhook yang gagal
Modern PetstoreAPI mengimplementasikan semua pola ini. Periksa dokumentasi webhook untuk contoh lengkap.
Uji webhook Anda dengan Apidog untuk memverifikasi logika percobaan ulang, tanda tangan, dan idempoten sebelum masuk ke produksi.
FAQ
Berapa banyak upaya percobaan ulang yang harus dimiliki webhook?
5-10 kali percobaan dengan backoff eksponensial. Ini mencakup pemadaman sementara (5-17 menit) tanpa membebani klien.
Haruskah webhook mencoba ulang pada kesalahan 4xx?
Tidak. Kesalahan 4xx menunjukkan masalah klien (URL salah, kegagalan autentikasi). Mencoba ulang tidak akan memperbaikinya. Hanya coba ulang kesalahan 5xx dan kegagalan jaringan.
Berapa lama batas waktu webhook seharusnya?
Maksimal 5 detik. Klien harus segera mengembalikan respons 200 dan memproses secara asinkron. Batas waktu yang lebih lama menunjukkan bahwa klien melakukan pemrosesan sinkron.
Bagaimana jika klien tidak pernah merespons webhook?
Setelah percobaan ulang maksimum, pindahkan ke antrean pesan gagal. Beri tahu klien melalui email. Pertimbangkan untuk menonaktifkan webhook untuk klien tersebut setelah kegagalan berulang.
Haruskah URL webhook menggunakan HTTPS?
Ya, selalu wajib menggunakan HTTPS. Webhook HTTP dapat dicegat dan dimodifikasi. Modern PetstoreAPI menolak URL webhook HTTP.
Bagaimana cara mencegah serangan replay?
Sertakan cap waktu dalam payload dan tolak webhook yang berusia lebih dari 5 menit. Gabungkan dengan verifikasi tanda tangan.
Bisakah klien meminta pengiriman ulang webhook?
Ya. Modern PetstoreAPI menyediakan endpoint untuk mengirim ulang webhook tertentu: POST /webhooks/{id}/redeliver
Bagaimana cara menguji webhook secara lokal?
Gunakan alat seperti ngrok untuk mengekspos localhost ke internet, atau gunakan server mock Apidog untuk mensimulasikan endpoint webhook selama pengembangan.
