Webhook & Event Pengiriman
Hubina mengirim setiap perubahan status pengiriman ke endpoint HTTPS milik Anda sebagai POST JSON yang ditandatangani. Halaman ini menjelaskan cara mendaftarkan endpoint, daftar event, format payload, verifikasi tanda tangan, kebijakan percobaan ulang, dan bagaimana Hubina sendiri menerima laporan dari penyedia.
Mendaftarkan endpoint
- Buka Dashboard → Webhooks.
- Isi Nama endpoint dan URL endpoint — wajib HTTPS dan alamat publik.
- Pilih tipe event yang ingin diterima, lalu simpan. Signing secret ditampilkan satu kali; salin dan simpan di server Anda.
Setiap endpoint memiliki tombol Kirim tes (mengirim event webhook.test dan langsung menampilkan hasil HTTP-nya), Riwayat (10 percobaan terakhir), serta aksi nonaktifkan/aktifkan, rotasi secret, dan hapus. Endpoint yang nonaktif tidak menerima event apa pun. Maksimal 10 endpoint per akun.
URL harus alamat publik
Permintaan dikirim dari server Hubina, jadi URL yang menunjuk ke alamat privat, loopback, atau link-local
(localhost, 10.x, 192.168.x, 169.254.x, dan sejenisnya),
memakai HTTP, atau memuat kredensial ditolak saat pendaftaran.
Tipe event
Pilih satu atau beberapa tipe; memilih semuanya berarti endpoint menerima seluruh laporan status pengiriman.
| Event | Kapan dikirim |
|---|---|
message.accepted | Penyedia menerima tanggung jawab atas pesan. Bukan berarti pesan sudah sampai. |
message.delivered | Pesan diterima server tujuan. |
message.failed | Kegagalan sementara, misalnya mailbox penuh atau penundaan di sisi penerima. |
message.bounced | Bounce permanen: alamat tujuan tidak ada atau menolak secara permanen. |
message.rejected | Penyedia menolak pesan sebelum menerimanya. |
message.complained | Penerima menandai pesan sebagai spam. |
webhook.test | Event uji dari tombol Kirim tes; selalu dikirim tanpa memandang pilihan tipe. |
Format payload
{
"id": "whd_9f2c1d7a4b6e8f0a1c2d3e4f5a6b7c8d",
"type": "message.delivered",
"created_at": "2026-10-16T10:12:44.382Z",
"data": {
"message_id": "msg_1a2b3c4d5e6f708192a3b4c5d6e7f809",
"status": "delivered",
"recipient": "[email protected]",
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Struk pembayaran",
"severity": null,
"reason": null,
"provider_event_id": "evt-9f2c1d7a",
"occurred_at": "2026-10-16T10:12:44.000Z",
"test": false
}
}| Header | Isi |
|---|---|
Hubina-Event | Tipe event, misalnya message.delivered. |
Hubina-Delivery | ID percobaan pengiriman, sama dengan id di payload — pakai untuk deduplikasi. |
Hubina-Timestamp | Waktu tanda tangan dibuat (detik Unix). |
Hubina-Signature | t=<timestamp>,v1=<hmac>. |
Isi pesan (teks/HTML) tidak pernah dikirim ke webhook dan tidak disimpan Hubina — payload hanya membawa metadata rute dan status.
Verifikasi tanda tangan
Tanda tangan adalah HMAC-SHA256 dari {timestamp}.{raw body} memakai signing secret endpoint. Bandingkan dengan perbandingan waktu-tetap, dan hitung atas raw body yang diterima (bukan hasil parsing ulang), karena urutan kunci dapat berubah.
import crypto from 'node:crypto';
function verifyWebhook(rawBody, headers, signingSecret) {
const header = headers['hubina-signature'] || '';
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
if (!parts.t || !parts.v1) return false;
const expected = crypto
.createHmac('sha256', signingSecret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
const given = Buffer.from(parts.v1);
const wanted = Buffer.from(expected);
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
}- Tolak yang basi. Tanda tangan yang lebih tua dari 5 menit sebaiknya ditolak; event yang benar-benar hilang tetap dapat dikirim ulang oleh Hubina.
- Deduplikasi. Gunakan header
Hubina-Delivery(ataudata.message_id+type) supaya percobaan ulang tidak diproses dua kali. - Balas cepat. Cukup balas
2xxsetelah event disimpan; pekerjaan berat sebaiknya dilakukan asinkron.
Percobaan ulang dan penonaktifan
- Balasan
2xxberarti selesai. Semua balasan lain, redirect, timeout 10 detik, atau kegagalan koneksi dihitung sebagai gagal. - Percobaan ulang mengikuti jeda bertingkat: 30 detik, 2 menit, 10 menit, 30 menit, 2 jam, 6 jam, 12 jam, lalu 24 jam — delapan percobaan, setelah itu percobaan ditandai gagal permanen dan alasannya tersimpan di riwayat.
- Setelah 15 kegagalan berurutan, endpoint dinonaktifkan otomatis agar antrean tidak tumbuh tanpa batas. Aktifkan kembali dari dashboard setelah penerima diperbaiki; penghitung kegagalan direset saat itu.
- Riwayat percobaan disimpan 30 hari, dan setiap baris menampilkan kode HTTP, waktu, jumlah percobaan, serta pesan error penerima.
Alur internal: laporan dari penyedia
Endpoint webhook pelanggan di atas adalah jalur keluar. Jalur masuk tetap sama seperti sebelumnya: penyedia mengirim laporan pengiriman ke Hubina, dan Hubina yang meneruskannya.
POST https://api.hubina.net/v1/hooks/mailgunEndpoint ini menerima laporan dari penyedia pengiriman (bukan dari aplikasi Anda), memverifikasi keasliannya, lalu mencatatnya ke pesan yang sesuai. Selain jalur push ini, job sinkronisasi berjalansetiap 5 menit untuk menarik ulang event yang gagal terkirim, sehingga event yang terlewat tetap tercatat dan diteruskan ke webhook Anda.
| Status | Kondisi | Isi |
|---|---|---|
200 | Payload valid dan diproses | { "status": "ok", "received": n, "recorded": n, "duplicates": n, "unmatched": n } |
400 | Payload tidak memuat event yang bisa diproses | invalid_request |
401 | Signature tidak valid atau kedaluwarsa | invalid_signature / stale_timestamp |
429 | Melebihi batas 600 permintaan per menit per alamat IP | rate_limited |
503 | Signing key penyedia belum dikonfigurasi | webhook_not_configured |
- Dua bentuk amplop. Payload modern (
event-datasebagai objek maupun array) dan form legacy sama-sama diterima. - Deduplikasi. Setiap event penyedia dikunci oleh ID event (atau digest deterministik bila tidak ada), jadi percobaan ulang tidak pernah menggandakan riwayat atau tagihan.
- Status monoton. Status pesan hanya bergerak maju:
acceptedyang telat tidak menurunkandelivered, dancomplainedtetap yang terkuat. - Satu sumber kebenaran. Setiap event masuk ke timeline pesan sebelum diteruskan, sehingga Sending & Logs dan webhook Anda menceritakan hal yang sama.