Amankan Webhook Email Transaksional — Verifikasi Signature HMAC & Cegah Serangan SSRF
Webhook itu pintu masuk yang terbuka ke server Anda. Email API Anda — MailAnvil, Resend, Mailgun, semua — akan POST payload JSON ke URL yang Anda daftarkan setiap kali ada bounce, complaint, atau delivery. Masalahnya: siapa pun di internet juga bisa POST ke URL itu.
Kalau Anda memproses payload tanpa verifikasi, penyerang bisa menyuntik event palsu. Bayangkan: server Anda menerima email.delivered palsu untuk email yang tidak pernah terkirim, lalu menampilkan "terkirim" ke pelanggan. Atau lebih buruk — event email.complained palsu yang memicu Anda menonaktifkan pengguna yang tidak pernah komplain.
Ada dua pertahanan yang wajib, dan keduanya sederhana.
1. Verifikasi signature HMAC
Setiap webhook MailAnvil dikirim dengan header X-MailAnvil-Signature. Nilainya adalah HMAC-SHA256 dari body mentah (raw bytes, sebelum di-parse jadi JSON), ditandatangani dengan secret unik yang Anda dapat saat membuat webhook.
Kenapa harus HMAC dari body mentah, bukan === biasa? Dua alasan:
- Keaslian — hanya pemilik
secretyang bisa menghasilkan signature valid. Penyerang yang menebak URL Anda tetap tidak bisa memalsukan event tanpa secret. - Integritas — kalau body berubah satu byte pun di tengah jalan (MITM, proxy, bug), signature tidak cocok dan Anda tahu payload rusak.
Verifikasi di Node.js (Hono/Express/Fastify)
import { createHmac, timingSafeEqual } from 'node:crypto';
app.post('/webhooks/mailanvil', async (req) => {
const signature = req.headers['x-mailanvil-signature'];
const rawBody = await req.text(); // HARUS raw string, bukan JSON.parse
const expected = createHmac('sha256', process.env.MAILANVIL_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
// timingSafeEqual, JANGAN pakai ===
if (!signature || !timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
)) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
// ... proses event
});
Tiga hal yang bikin pola ini benar:
rawBodybukanJSON.parse— parsereq.bodydulu baru di-sign ulang akan menghasilkan string berbeda (spasi, urutan key, encoding berubah). Sign-nya harus dari bytes persis yang diterima.timingSafeEqual, bukan===— perbandingan===berhenti di karakter pertama yang beda, jadi makin benar prefix-nya makin lama responsnya. Penyerang bisa menebak secret byte-per-byte lewat timing.timingSafeEqualmembandingkan seluruh string dengan waktu konstan.- Tolak dulu, proses belakangan — verifikasi sebelum menyentuh payload. Jangan pernah log, parse, atau react ke body yang belum lolos verifikasi.
Kalau secret bocor (di-commit ke git, ter-log, ketulis di client), rotasi segera dan anggap semua event lama tidak terpercaya.
2. Cegah SSRF di sisi pengirim
SSRF (Server-Side Request Forgery) adalah sisi lain dari koin yang sama — bukan menyerang endpoint Anda, tapi menyalahgunakan server pengirim. Kalau email API memanggil URL webhook Anda atas nama Anda, penyerang bisa coba daftarkan URL yang menunjuk ke 127.0.0.1, 169.254.169.254 (metadata cloud), atau IP privat lain, lalu memancing server mengakses layanan internal.
API yang serius memvalidasi URL webhook saat dibuat dan saat dikirim. MailAnvil menolak:
localhost,127.0.0.1,0.0.0.0- Seluruh blok IPv4 privat/reserved:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,100.64.0.0/10, multicast, dan lain-lain - IPv6 loopback/link-local (
::1,fe80::/10,fc00::/7) - Untuk nama domain, IP hasil resolve DNS ikut dicek — kalau
webhook.kamu.comresolve ke IP privat, ditolak
Validasi berlapis: sekali saat Anda mendaftarkan URL, dan sekali lagi saat mengirim event (DNS bisa berubah setelah pendaftaran). Defense-in-depth.
Pola lengkapnya
Untuk server yang menerima webhook, checklist keamanan minimal:
- Verifikasi HMAC signature dari body mentah (wajib, selalu).
- Pakai
timingSafeEqualuntuk perbandingan secret. - Validasi struktur payload (schema) — jangan percaya
eventdandatabegitu saja. - Log signature yang gagal — itu sinyal seseorang sedang probing endpoint Anda.
- Simpan secret di env/secret-manager, jangan di kode.
Webhook email transaksional hanya seaman endpoint yang menerimanya. HMAC + SSRF guard menutup dua celah paling umum. Sisanya adalah disiplin: verifikasi dulu, percaya belakangan.