Jawaban singkat: Untuk verifikasi webhook signature OngkirHub, baca header OngkirHub-Signature yang berisi t (waktu Unix) dan v1 (tanda tangan). Hitung HMAC SHA-256 atas t, titik, dan raw body dengan secret webhook kamu, lalu bandingkan hasilnya dengan v1 secara timing-safe. Tolak kiriman yang tanda tangannya berbeda atau waktunya lebih dari sekitar 5 menit.
Webhook OngkirHub mengirim kabar ke server kamu saat resi terbit, status pengiriman berubah, atau top-up dibayar. Karena URL webhook bisa diakses publik, siapa pun bisa mencoba mengirim data palsu ke sana. Verifikasi webhook signature memastikan setiap kiriman benar berasal dari OngkirHub dan isinya tidak diubah di jalan. Panduan ini melanjutkan tutorial pertama Developer API OngkirHub.
Yang kamu butuhkan
Kamu butuh tiga hal: endpoint HTTPS, secret webhook, dan akses ke raw body permintaan.
- Endpoint HTTPS publik. URL harus bisa diakses dari internet, bukan alamat privat, loopback, atau host internal, tanpa user dan password di dalam URL, dan maksimal 500 karakter.
- Secret webhook. Secret berawalan
whsec_diikuti 64 karakter heksadesimal, ditampilkan saat endpoint dibuat. Simpan di variabel lingkungan, bukan di kode sumber. - Raw body. Raw body adalah teks permintaan persis seperti yang diterima, sebelum diubah menjadi objek JSON. OngkirHub mengirim JSON ringkas tanpa spasi, dan tanda tangan dihitung dari teks itu. Kalau body sudah di-parse lalu diubah lagi menjadi teks, urutan atau spasinya bisa berubah dan verifikasi gagal.
Pastikan juga jam server sinkron, karena verifikasi memeriksa selisih waktu antara server kamu dan nilai t di header.
Langkah integrasi
Pasang verifikasi sebelum logika bisnis apa pun menyentuh isi event.
- Buka menu Developer di OngkirHub App, lalu tambahkan URL endpoint webhook.
- Pilih minimal satu event yang ingin diterima. Satu workspace bisa memiliki sampai 10 endpoint.
- Salin secret
whsec_dan simpan di variabel lingkungan server. - Verifikasi tanda tangan setiap permintaan sebelum memproses isinya.
- Balas dengan status 2xx secepat mungkin setelah verifikasi lolos.
- Proses isi event di antrean milikmu, bukan di dalam permintaan webhook.
- Kirim tes dari halaman Developer, lalu cek hasilnya di log pengiriman.
Respons selain 2xx dianggap gagal. Batas tunggunya 10 detik, redirect tidak diikuti, dan error koneksi juga dihitung gagal. Karena itu, pekerjaan berat seperti memperbarui pesanan atau mengirim email sebaiknya dipindahkan ke antrean.
Daftar event webhook
Ada tujuh event yang bisa dipilih, ditambah satu event khusus untuk tes. Tabel berikut merangkum kapan masing-masing dikirim.
| Event | Dikirim saat |
|---|---|
shipment.booked |
Pengiriman diterima kurir dan berstatus booked |
shipment.awb_ready |
Nomor resi terbit |
shipment.status_updated |
Status berubah ke picked_up, in_transit, out_for_delivery, problem, returned, atau cancel_requested |
shipment.delivered |
Paket terkirim ke penerima |
shipment.failed |
Pemesanan gagal sebelum resi terbit |
shipment.cancelled |
Pembatalan dikonfirmasi |
topup.paid |
Top-up saldo sudah dibayar |
webhook.test |
Hanya dari tombol Kirim tes di halaman Developer |
Setiap kiriman membawa header OngkirHub-Event berisi nama event dan OngkirHub-Delivery berisi id pengiriman webhook, dengan User-Agent OngkirHub-Webhooks/1.0. Body-nya memiliki lima field di tingkat atas: id berawalan evt_, type, createdAt, livemode, dan data.
Untuk event shipment.*, objek data berisi shipment dan previousStatus. Untuk topup.paid, data berisi topup dan balance.available. Contoh di bawah ini dipangkas agar ringkas, dan isi lengkap objek shipment ada di panduan webhook di Docs.
{"id":"evt_xxx","type":"shipment.awb_ready","createdAt":"2026-10-09T03:15:00.000Z","livemode":false,"data":{"previousStatus":"booked","shipment":{"id":"OHxxxxxxxx","status":"awb_ready","awb":"NOMORRESI"}}}
Contoh verifikasi dengan TypeScript
Header OngkirHub-Signature berformat t=<detik unix>,v1=<hex>. Nilai v1 adalah HMAC SHA-256 dalam huruf heksadesimal kecil sepanjang 64 karakter, dihitung atas teks t + "." + raw body. Kuncinya adalah seluruh teks secret, termasuk awalan whsec_, dipakai apa adanya tanpa didekode.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SEC = 300; // misalnya 5 menit
export function verifyOngkirHubSignature(header: string | undefined, rawBody: string, secret: string): boolean {
if (!header) return false;
const parts = new Map(
header.split(",").map((part) => {
const [key, ...rest] = part.trim().split("=");
return [key, rest.join("=")] as [string, string];
}),
);
const t = Number(parts.get("t"));
const v1 = parts.get("v1") ?? "";
if (!Number.isInteger(t) || !/^[0-9a-f]{64}$/.test(v1)) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > TOLERANCE_SEC) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
Contoh pemakaian dengan Express, memakai express.raw supaya body tetap berupa teks asli:
import express from "express";
import { verifyOngkirHubSignature } from "./verify";
const app = express();
app.post("/webhooks/ongkirhub", express.raw({ type: "application/json" }), async (req, res) => {
const rawBody = req.body.toString("utf8");
const secret = process.env.ONGKIRHUB_WEBHOOK_SECRET ?? "whsec_xxx";
if (!verifyOngkirHubSignature(req.get("OngkirHub-Signature"), rawBody, secret)) {
return res.status(400).end();
}
const event = JSON.parse(rawBody);
await enqueue(event); // fungsi antrean milikmu
res.status(200).end();
});
Ada dua detail penting di kode itu. Pertama, timingSafeEqual mensyaratkan panjang kedua nilai sama dan akan melempar error bila berbeda, jadi panjang v1 dicek lebih dulu lewat pola 64 karakter. Kedua, pemeriksaan selisih waktu mencegah replay, yaitu kiriman lama yang direkam lalu dikirim ulang oleh pihak lain. Toleransi 5 menit pada contoh adalah saran, bukan ketentuan wajib.
Kenapa verifikasi webhook signature gagal?
Hampir semua kegagalan verifikasi berasal dari body yang berubah sebelum dihitung, atau dari secret yang tidak cocok. Periksa daftar berikut secara berurutan.
- Body sudah di-parse. Middleware JSON bawaan framework mengubah body menjadi objek lebih dulu. Pakai pembaca raw body khusus untuk rute webhook, seperti
express.rawpada contoh. - Encoding berbeda. Ubah buffer ke teks dengan UTF-8, tanpa memangkas spasi atau baris baru.
- Secret salah. Setiap endpoint punya secret sendiri. Pastikan kamu memakai secret endpoint yang benar, lengkap dengan awalan
whsec_, tanpa spasi tambahan saat disalin. - Format hasil berbeda. Bandingkan dengan hex huruf kecil, bukan base64 atau hex huruf besar.
- Jam server melenceng. Bila jam server berselisih jauh, pemeriksaan selisih waktu akan menolak kiriman yang sebenarnya sah. Sinkronkan jam server secara otomatis.
- Proxy mengubah body. Pastikan reverse proxy atau layanan perantara di depan servermu meneruskan body apa adanya.
Menangani error, retry, dan idempotensi
Webhook OngkirHub dikirim setidaknya sekali (at-least-once). Artinya, satu event bisa tiba lebih dari sekali, jadi sistemmu harus aman menerima duplikat.
Bila kiriman gagal, OngkirHub mencoba ulang otomatis dengan jeda 1 menit, 5 menit, 30 menit, 2 jam, 6 jam, dan 24 jam. Totalnya tujuh percobaan dalam rentang sekitar 32 jam 36 menit. Antrean pengiriman diproses setiap menit, jadi waktu nyatanya kira-kira. Jadwal resminya tercantum di panduan webhook di Docs.
Pada setiap percobaan, nilai t dibuat ulang, sedangkan body tetap sama. Karena body sama, id event juga sama. Pola yang aman:
- Simpan
idevent yang sudah diproses, misalnya di tabel dengan indeks unik, lalu abaikan event denganidyang sama. - Jangan bergantung pada urutan kedatangan event. Bila urutan penting, bandingkan
createdAtatau ambil status terbaru lewatGET /v1/shipments/{id}. - Balas 2xx untuk event duplikat yang sudah pernah diproses, supaya OngkirHub tidak terus mencoba ulang.
Endpoint dinonaktifkan otomatis setelah 20 kegagalan berturut-turut, dan hitungannya kembali ke nol setiap kali ada kiriman yang sukses. Notifikasi Webhook dinonaktifkan muncul di aplikasi. Setelah server diperbaiki, aktifkan lagi endpoint di halaman Developer dan gunakan tombol Kirim ulang di log pengiriman untuk event yang terlewat.
Uji dengan kunci test
Pengiriman yang dibuat dengan kunci test memicu event sungguhan dengan livemode bernilai false, tanpa pengiriman fisik. Ini cara paling realistis untuk menguji alur webhook dari awal sampai akhir.
- Tombol Kirim tes di halaman Developer mengirim event
webhook.testdenganlivemodebernilaifalsedan pesan tes didata. - Log pengiriman di halaman Developer menampilkan hasil setiap kiriman, dan dari sana kamu bisa mengirim ulang event tertentu.
- Periksa field
livemodedi kode agar event uji tidak tercampur dengan data produksi. - Saat mengembangkan di komputer lokal, gunakan URL HTTPS publik sementara yang meneruskan permintaan ke mesinmu, karena endpoint webhook harus bisa diakses dari internet.
Konsep dasar webhook, termasuk bedanya dengan polling, dibahas di apa itu webhook status pengiriman. Ringkasan fitur integrasi untuk tim non-teknis ada di halaman Developer.