Webhook adalah salah satu cara paling ampuh untuk menerima pembaruan secara real-time dari layanan pihak ketiga. Satu HTTP POST dari Stripe, GitHub, Shopify, atau Twilio dapat memicu logika bisnis penting dalam aplikasi Anda — menagih pelanggan, memperbarui repositori, mengirimkan pesanan, atau mengirim SMS konfirmasi.
Namun, setiap permintaan webhook tiba melalui internet publik. Dan itu berarti siapa pun yang menebak atau menemukan URL webhook Anda dapat mengirim muatan berbahaya yang terlihat sepenuhnya sah. Tanpa otentikasi yang tepat, aplikasi Anda tidak memiliki cara untuk membedakan antara peristiwa nyata dan yang dipalsukan.
Di sinilah **verifikasi tanda tangan webhook** berperan. Ini adalah mekanisme standar yang sederhana yang memastikan setiap permintaan webhook yang masuk benar-benar dari layanan yang Anda harapkan dan belum diubah dalam perjalanan.
Dalam panduan komprehensif ini, Anda akan mempelajari dengan tepat cara kerja verifikasi tanda tangan webhook, dan cara mengimplementasikannya dengan benar dalam bahasa pemrograman populer. Anda juga akan melihat kesalahan umum yang harus dihindari dan cara menguji semuanya secara end-to-end — dengan cepat dan andal.
Apa Itu Verifikasi Tanda Tangan Webhook?
Verifikasi tanda tangan webhook adalah proses konfirmasi bahwa permintaan webhook yang masuk benar-benar berasal dari layanan yang Anda harapkan dan belum diutak-atik.
Sebagian besar penyedia menggunakan HMAC (Hash-based Message Authentication Code) dengan SHA-256 atau SHA-512. Layanan menghitung:
signature = HMAC-SHA256(secret_key, payload)
Kemudian mereka mengirimkan tanda tangan di header (biasanya X-Signature, Signature, atau X-Hub-Signature-256).
Server Anda:
- Menerima muatan sebagai byte mentah (penting!)
- Menghitung ulang HMAC menggunakan secret yang Anda simpan
- Membandingkan tanda tangan yang dihitung dengan yang diterima
Jika keduanya cocok persis, Anda memproses webhook tersebut. Jika tidak, Anda mengembalikan HTTP 401 atau 403.
Mengapa HMAC-SHA256 Adalah Standar Industri
Penyedia memilih HMAC-SHA256 untuk alasan yang baik:
- Cepat: Bahkan pada perangkat keras sederhana, ini sangat cepat.
- Aman: SHA-256 tetap tidak terpecahkan pada tahun 2025.
- Sederhana: Satu kunci rahasia, tidak ada pasangan kunci publik/pribadi untuk dikelola.
- Standar: Pustaka dalam setiap bahasa mengimplementasikannya dengan benar.
GitHub, Stripe, Shopify, Slack, dan lusinan lainnya semuanya menggunakan HMAC-SHA256.
Cara Mengimplementasikan Verifikasi Tanda Tangan Webhook di Node.js
Mari kita mulai dengan contoh dunia nyata di Node.js.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const computedSignature = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(signature)
);
}
Poin-poin penting yang perlu diperhatikan:
- Selalu gunakan
crypto.timingSafeEqualuntuk mencegah serangan waktu. - Jangan pernah menggunakan
===pada string — itu rentan. - Gunakan
Buffer.from()untuk memastikan perbandingan waktu konstan.
Contoh middleware Express:
app.post('/webhooks/stripe', (req, res, next) => {
const signature = req.headers['stripe-signature'];
const secret = process.env.STRIPE_WEBHOOK_SECRET;
// Dapatkan badan mentah (Express membutuhkan middleware untuk menjaga badan mentah)
const rawBody = req.rawBody || req.body; // gunakan body-parser dengan opsi verifikasi
if (!verifyWebhookSignature(rawBody, signature, secret)) {
return res.status(401).send('Tanda tangan tidak valid');
}
// Tanda tangan valid → proses event
next();
});
Implementasi Python (FastAPI + Pydantic)
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
app = FastAPI()
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
@app.post("/webhooks/github")
async def github_webhook(request: Request):
signature = request.headers.get("X-Hub-Signature-256")
if not signature:
raise HTTPException(status_code=401, detail="Tanda tangan hilang")
payload = await request.body()
if not verify_signature(payload, signature.split('=')[1], SECRET):
raise HTTPException(status_code=401, detail="Tanda tangan tidak valid")
# Proses webhook
return {"status": "ok"}
Kesalahan Umum yang Dilakukan Pengembang (dan Cara Menghindarinya)
1. Menggunakan JSON.stringify() atau Badan yang Diurai
Banyak framework mengurai JSON secara otomatis. Ini membatalkan verifikasi karena spasi kosong, urutan kunci, dan format yang berbeda.
Solusi: Selalu tangkap badan mentah sebelum mengurai.
Di Express: Gunakan body-parser dengan { verify: true }
Di FastAPI: Gunakan await request.body()
2. Membandingkan String dengan ===
Serangan waktu dapat membocorkan informasi. Gunakan crypto.timingSafeEqual atau hmac.compare_digest.
3. Menyimpan Secret di Kode
Gunakan variabel lingkungan atau manajer secret (AWS Secrets Manager, HashiCorp Vault, dll.).
4. Lupa Menangani Serangan Replay
Sebagian besar penyedia menyertakan stempel waktu. Periksa apakah event tersebut baru (misalnya, dalam waktu 5 menit).
const timestamp = req.headers['X-Signature-Timestamp'];
if (Date.now() - timestamp > 5 * 60 * 1000) {
return res.status(401).send('Stempel waktu terlalu tua');
}
5. Menggunakan SHA-1 (Masih Terjadi!)
GitHub menghentikan SHA-1 pada tahun 2022. Selalu gunakan SHA-256.
Menguji Verifikasi Tanda Tangan Webhook dengan Apidog
Menguji webhook secara manual itu menyakitkan. Anda mengirim permintaan, memeriksa log, memperbaiki, mengulang.
Apidog membuatnya mudah:
Di proyek Apidog Anda, klik ikon + di sidebar kiri dan pilih "New Other Protocol APls" > "Webhook".
Setelah membuat Webhook, isi bidang-bidang berikut di editor:Metode Permintaan: Biasanya POST.Nama Webhook: Ini akan muncul di dokumentasi API dan ekspor OpenAPI, mis. order.URL Debug (opsional): URL aktual yang digunakan untuk mengirim permintaan uji. Catatan: Ini hanya untuk tujuan pengujian dan tidak akan disertakan dalam dokumentasi.Info Lainnya: Seperti badan permintaan.
Klik Simpan setelah Anda mengisi semua bidang yang diperlukan.
Cukup masukkan URL Webhook Anda ke bidang URL Debug, lalu klik Kirim untuk mensimulasikan panggilan Webhook.
Saya telah menghemat berjam-jam debugging dengan simulator webhook Apidog. Ini bahkan mendukung format stripe-signature Stripe yang persis dan awalan sha256=... GitHub.
Contoh Dunia Nyata: Memverifikasi Webhook Stripe
Stripe menggunakan format header khusus:
stripe-signature: t=1681234567,v1=abc123...,v0=def456...
Anda harus:
- Mengekstrak stempel waktu
t= - Mengekstrak tanda tangan
v1=(disukai) - Menghitung ulang menggunakan
payload + timestamp - Membandingkan hanya bagian
v1
Stripe menyediakan pustaka resmi untuk menangani kerumitan ini:
const stripe = require('stripe')('sk_...');
stripe.webhooks.constructEvent(payload, sigHeader, endpointSecret);
Tetapi memahami HMAC yang mendasarinya sangat penting ketika Anda perlu mengimplementasikannya sendiri.
Topik Lanjutan: Mentolerir Banyak Tanda Tangan
Beberapa penyedia (seperti Stripe) mengirimkan banyak tanda tangan untuk kompatibilitas mundur. Kode Anda harus:
- Membagi header berdasarkan
, - Mencoba setiap satu
- Menerima jika ada yang cocok
Praktik Terbaik Keamanan pada Tahun 2025
- Rotasi secret webhook setiap 90 hari.
- Gunakan secret berumur pendek jika memungkinkan.
- Catat verifikasi yang gagal tetapi jangan pernah mencatat secret.
- Batasi kecepatan endpoint webhook untuk mencegah serangan brute-force.
- Selalu gunakan HTTPS.
Kesimpulan: Langkah Verifikasi Kecil, Keuntungan Keamanan Besar
Verifikasi tanda tangan webhook terdengar seperti detail kecil. Namun, itu adalah perbedaan antara aplikasi yang aman dan aplikasi yang dapat dieksploitasi dengan mudah oleh penyerang.
Implementasikan dengan benar, uji secara menyeluruh dengan alat seperti Apidog, dan tidur lebih nyenyak mengetahui integrasi Anda terlindungi.
Unduh Apidog secara gratis hari ini dan verifikasi webhook pertama Anda dalam waktu kurang dari 5 menit. Ini adalah cara tercepat untuk membuktikan bahwa kode Anda benar-benar berfungsi.
