Email webhook adalah mekanisme yang memberi tahu aplikasi Anda saat ada perubahan status pada email yang dikirim. Berbeda dengan polling API yang boros resource, webhook push notifikasi langsung ke server Anda setiap kali ada event. Artikel ini menjelaskan cara setup webhook endpoint, verify signature untuk keamanan, handle retry saat pengiriman gagal, dan menyimpan status email secara real-time di database aplikasi Anda.
Saat Anda mengirim emailtransaksional melalui SMTP, satu-satunya feedback yang Anda dapat adalah apakah email berhasil dikirim dari server Anda atau tidak. Setelah itu, email berada di tangan provider tujuan. Anda tidak tahu apakah email itu sampai di inbox, masuk spam, atau ditolak sama sekali.
Ini adalah masalah nyata untuk aplikasi yang bergantung pada status email. Bayangkan sistem OTP yang perlu tahu apakah kode sudah sampai ke pengguna. Atau sistem invoice yang harus tahu apakah email sudah dibaca. Tanpa informasi ini, Anda mengambang di antara blind spots.
Email webhook menyelesaikan masalah ini.
Daftar Isi
Apa Itu Email Webhook dan Bedanya dengan Polling API?
Webhook adalah mekanisme push. Saat terjadi event tertentu, server provider email akan mengirim HTTP POST ke endpoint yang Anda tentukan. Anda tidak perlu minta data terus-menerus; data datang sendiri saat ada perubahan.
Polling API bekerja berbeda. Aplikasi Anda secara berkala bertanya ke API provider email untuk minta status terbaru. Même kalau tidak ada perubahan, request tetap dikirim. Ini menambah latency dan membebani server Anda dengan request yang sering tidak perlu.
Webhook email standard membawa payload JSON dengan field-field ini:
event: tipe event (delivered, bounce, complaint, open, click)timestamp: kapan event terjadi dalam format Unix timestampto: alamat email recipientfrom: alamat email pengirimsubject: subject line email originalmessage_id: identifier unik dari provider untuk tracking
Beda fundamental dengan polling: Anda dapat informasi saat itu juga, bukan saat Anda kebetulan bertanya.
Kenapa Webhook Penting untuk Aplikasi Transactional Email?
Aplikasi yang mengirim OTP, konfirmasi order, password reset, atau invoice bukan hanya butuh email terkirim. Aplikasi ini butuh tahu pasti email sudah sampai dan kapan recipient membukanya.
Tanpa webhook, Anda tidak punya visibility ke masalah-masalah ini:
- Email masuk spam folder tanpa notifikasi ke aplikasi
- Kode OTP tidak sampai karena hard bounce yang tidak terdeteksi
- User menandai email sebagai spam (complaint) tapi Anda tidak tahu
- Invoice email tidak dibuka dalam 7 hari, perlu reminder follow-up
Masalah paling kritis adalah bounce detection. Kalau Anda tidak tahu email hard bounce, Anda akan terus kirim ke alamat yang sudah tidak aktif. Lama-lama provider email menandai domain Anda sebagai sender yang tidak bisa dipercaya.
Langkah Pertama: Setup Endpoint Webhook
Sebelum register webhook di provider email, Anda perlu endpoint yang bisa menerima request dari provider tersebut. Endpoint ini harus:
- HTTPS (provider email modern tidak kirim ke HTTP plain)
- Bisa menerima HTTP POST dengan JSON body
- Merespons dengan HTTP 200 dalam 30 detik
- idempotent: menerima event yang sama berkali-kali tidak menyebabkan duplicate processing
Contoh endpoint sederhana di Node.js dengan Express:
app.post('/webhook/email', async (req, res) => {
const payload = req.body;
console.log('Webhook received:', payload.event, payload.to);
// Process event
await processEmailEvent(payload);
// Respond immediately
res.status(200).send('OK');
});
Contoh yang sama di Laravel:
Route::post('/webhook/email', function (Request $request) {
$payload = $request->all();
Log::info('Webhook received: ' . $payload['event']);
ProcessEmailEvent::dispatch($payload);
return response('OK');
});
Perhatikan bahwa webhook handler langsung merespons 200 dan memproses secara asynchronous via queue. Ini penting karena provider webhook punya timeout: kalau Anda proses secara synchronous dan butuh waktu lama, provider bisa menganggap request gagal dan retry.
Cara Verify Signature Webhook untuk Keamanan
Endpoint webhook yang menerima request dari provider email tanpa verifikasi adalah target utama untuk spoofing attack. Siapa pun yang tahu URL endpoint Anda bisa mengirim event palsu dan membuat aplikasi Anda berpikir email berhasil dikirim padahal tidak.
Cara mengatasi ini adalah signature verification. Provider email modern menandatangani setiap webhook request dengan HMAC-SHA256 menggunakan secret key yang hanya Anda dan provider yang tahu.
Contoh verifikasi signature di Node.js:
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhook/email', (req, res) => {
const signature = req.headers['x-provider-signature'];
const rawBody = req.rawBody; // Perlu di-set saat parse body
if (!verifySignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process valid webhook
res.status(200).send('OK');
});
Di Laravel, verifikasi signature biasanya dilakukan dengan middleware yang disediakan oleh package atau dengan logika manual:
function verifySignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
Setiap provider email punya nama header signature yang berbeda. Postmark pakai X-Postmark-Event-Signature, SendGrid pakai X-TWILIO-EMAIL-MESSAGE-VID, KIRIM.EMAIL pakai X-Kirim-Email-Signature. Cek dokumentasi provider Anda untuk pasti.
Handle Retry Saat Webhook Gagal Dikirim
Provider email tidak berhenti kalau endpoint Anda temporarily down. Mereka punya retry policy yang pengiriman ulangi request berkali-kali sampai berhasil atau sampai maximum attempts tercapai.
Retry policy standard:
- Attempt 1: langsung saat event terjadi
- Attempt 2: 30 detik kemudian
- Attempt 3: 5 menit kemudian
- Attempt 4: 30 menit kemudian
- Attempt 5: 2 jam kemudian
Kalau endpoint Anda down selama 1 jam, attempt 1 dan 2 akan gagal, tapi attempt 3 sampai 5 akan menunggu dan eventual akan sukses begitu endpoint kembali online.
Implikasinya: webhook handler Anda harus idempotent. Receiving event yang sama dua kali tidak boleh menyebabkan duplikasi di database atau salah processing. Setiap event webhook punya unique message ID; cek ID ini sebelum processing untuk avoid duplicate.
Contoh pattern idempotent di Laravel:
Route::post('/webhook/email', function (Request $request) {
$messageId = $request->input('MessageId');
// Cek apakah event ini sudah diproses sebelumnya
if (WebhookEvent::where('message_id', $messageId)->exists()) {
return response('OK'); // Already processed, return success
}
// Simpan event ke database
WebhookEvent::create([
'message_id' => $messageId,
'event' => $request->input('event'),
'payload' => $request->all(),
'processed_at' => now(),
]);
ProcessEmailEvent::dispatch($request->all());
return response('OK');
});
Pattern yang sama di Node.js:
app.post('/webhook/email', async (req, res) => {
const { MessageId, event, To } = req.body;
// Check if already processed
const exists = await db.webhook_events.findOne({ message_id: MessageId });
if (exists) {
return res.status(200).send('OK');
}
// Store event
await db.webhook_events.create({
message_id: MessageId,
event,
recipient: To,
processed_at: new Date(),
});
// Queue for processing
await processEmailEvent(req.body);
res.status(200).send('OK');
});
Simpan Status Email Secara Real-Time di Database
Setelah webhook endpoint terima event, data perlu disimpan dengan struktur yang memungkinkan aplikasi query status email dengan cepat.
Struktur tabel yang praktikal:
CREATE TABLE email_status (
id BIGSERIAL PRIMARY KEY,
message_id VARCHAR(255) UNIQUE NOT NULL,
recipient VARCHAR(255) NOT NULL,
event VARCHAR(50) NOT NULL,
payload JSONB,
received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP
);
CREATE INDEX idx_email_status_recipient ON email_status(recipient);
CREATE INDEX idx_email_status_event ON email_status(event);
CREATE INDEX idx_email_status_received ON email_status(received_at);
Dengan tabel ini, aplikasi Anda bisa query:
- “Berapa email yang sudah delivered ke [email protected] dalam 30 hari terakhir?”
- “Apakah ada complaint event untuk domain gmail.com?”
- “Berapa rata-rata waktu dari sent ke delivered?”
Contoh query untuk cek status OTP:
$emailStatus = EmailStatus::where('recipient', $userEmail)
->where('event', 'delivered')
->where('received_at', '>=', now()->subMinutes(5))
->first();
if ($emailStatus) {
// Kode OTP sudah sampai
}
Event Webhook yang Perlu Di-Track untuk Transactional Email
Tidak semua event sama pentingnya untuk setiap use case. Berikut panduan event mana yang perlu di-track berdasarkan tipe aplikasi Anda.
Delivered adalah event wajib untuk semua aplikasi. Email berhasil sampai ke server tujuan. Ini confirmation bahwa email meninggalkan infrastruktur provider dan tidak bounce. Simpan delivered event untuk audit trail dan untuk tahu kapan harus stop retry.
Bounce ada dua tipe: hard bounce dan soft bounce. Hard bounce berarti alamat tidak valid permanent; hapus alamat ini dari mailing list dan stop pengiriman. Soft bounce biasanya temporary (mailbox full, server overloaded); provider akan retry sendiri, Anda tidak perlu action. Setiap bounce event berisi bounce type dan diagnostic code dari server tujuan.
Complaint terjadi saat recipient menandai email sebagai spam di client email mereka. Ini adalah signal serius: kalau complaint rate tinggi, domain Anda akan di-blacklist provider. Gmail dan Yahoo terutama sangat sensitive. Complaint event butuh action segera, biasanya hentikan pengiriman ke alamat tersebut.
Open dan Click adalah tracking events. Open tracker menyisipkan 1×1 pixel transparent image di email; ketika recipient download gambar ini, event open tercatat. Click tracking intercept semua link di email dan redirect melalui tracker URL. Data ini powerful untuk analytics tapi perlu informed consent dari pengguna.
Debug Webhook: Cara Test Endpoint Secara Lokal
Sebelum expose webhook ke production, Anda perlu test endpoint secara thorough. tiga tools yang paling praktikal.
Ngrok membuat local server accessible dari internet dengan tunneling. Jalankan ngrok http 3000, dapat URL publik, daftar URL itu sebagai webhook endpoint di provider. Semua request dari provider akan di-forward ke local machine Anda.
Requestbin adalah service yang buat endpoint publik dan simpan semua request yang masuk. Gunakan untuk test dan inspect payload yang dikirim provider tanpa perlu local server.
Postman atau curl untuk kirim test payload manual. Contoh dengan curl:
curl -X POST https://your-domain.com/webhook/email \
-H "Content-Type: application/json" \
-H "X-Provider-Signature: test-signature" \
-d '{"event":"delivered","MessageId":"test-123","to":"[email protected]"}'
Selalu test dengan payload yang realistis dari provider Anda. Format payload beda antar provider; yang terlihat sama sering punya nuansa yang bikin difference di parsing.
Webhook dengan payload format yang sudah documented seperti yang tersedia di KIRIM.EMAIL membuat testing lebih mudah karena struktur datanya konsisten dan bisa diprediksi.
FAQ
Apa itu email webhook dan bedanya dengan polling API?
Webhook adalah push mechanism: provider email mengirim HTTP POST ke endpoint Anda saat ada event. Polling API adalah pull mechanism: aplikasi Anda secara berkala bertanya ke API provider untuk cek status. Webhook lebih efisien karena data datang sendiri tanpa request yang tidak perlu.
Bagaimana cara setup endpoint webhook untuk email?
Endpoint harus HTTPS, bisa terima HTTP POST dengan JSON body, merespons HTTP 200 dalam 30 detik, dan idempotent. Setup di level aplikasi server dengan route yang arahkan ke handler yang dispatch event secara asynchronous.
Bagaimana handle retry saat webhook gagal dikirim?
Provider email retry secara otomatis dengan exponential backoff. Yang perlu Anda handle di aplikasi adalah memastikan endpoint idempotent: cek message_id sebelum processing untuk avoid duplicate. Simpan setiap event yang masuk ke database dengan unique constraint di message_id.
Bagaimana cara verify webhook signature untuk keamanan?
Signature verification pakai HMAC-SHA256. Provider email menandatangani setiap request dengan secret key yang hanya Anda dan provider tahu. Bandingkan signature dari header dengan expected signature yang Anda hitung sendiri. Kalau tidak match, reject request dengan 401.
Event webhook apa saja yang perlu di-track untuk transactional email?
Event paling penting untuk transactional email adalah delivered dan bounce. Complaint perlu di-track untuk maintain sender reputation. Open dan click adalah optional tracking yang butuh informed consent dari pengguna. Setiap event punya action item yang berbeda: delivered untuk confirmation, bounce untuk list cleanup, complaint untuk immediate suppression.
Bagaimana cara test webhook secara lokal tanpa expose endpoint publik?
Gunakan ngrok untuk buat tunnel dari internet ke local server. Atau pakai Requestbin untuk dapat endpoint publik yang menyimpan semua request masuk. postman atau curl untuk kirim manual test payload.
Apa bedanya webhook untuk bounce, complaint, dan delivery confirmation?
Delivery confirmation (delivered) berarti email berhasil sampai ke server tujuan. Bounce berarti email ditolak: hard bounce alamat tidak valid permanent, soft bounce temporary. Complaint berarti recipient menandai email sebagai spam di client mereka. Masing-masing butuh treatment berbeda: delivered untuk confirmation, hard bounce untuk list cleanup, soft bounce untuk retry, complaint untuk immediate suppression dan reputation monitoring.
Kalau Anda butuh email infrastructure yang mendukung webhook dengan retry policy handled secara otomatis, KIRIM.EMAIL Dev menyediakan dedicated IP dengan servers yang sepenuhnya di Indonesia. Anda dapat visibility penuh ke status setiap email tanpa perlu polling.
Hasbi Putra adalah Head of Marketing di KIRIM.EMAIL, email delivery infrastructure untuk developer dan tim IT di Indonesia. KIRIM.EMAIL mengirim lebih dari 11 juta email per hari dengan server yang sepenuhnya berlokasi di Indonesia.
- Cara Setup SMTP di WordPress untuk Email Transaksional Production - April 12, 2026
- Cara Integrate Email Webhook untuk Real-Time Email Status Tracking - April 10, 2026
- Cara Implementasi Email Rate Limiting untuk Menghindari Throttling - April 9, 2026
