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.

  1. Buka menu Developer di OngkirHub App, lalu tambahkan URL endpoint webhook.
  2. Pilih minimal satu event yang ingin diterima. Satu workspace bisa memiliki sampai 10 endpoint.
  3. Salin secret whsec_ dan simpan di variabel lingkungan server.
  4. Verifikasi tanda tangan setiap permintaan sebelum memproses isinya.
  5. Balas dengan status 2xx secepat mungkin setelah verifikasi lolos.
  6. Proses isi event di antrean milikmu, bukan di dalam permintaan webhook.
  7. 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.raw pada 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 id event yang sudah diproses, misalnya di tabel dengan indeks unik, lalu abaikan event dengan id yang sama.
  • Jangan bergantung pada urutan kedatangan event. Bila urutan penting, bandingkan createdAt atau ambil status terbaru lewat GET /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.test dengan livemode bernilai false dan pesan tes di data.
  • Log pengiriman di halaman Developer menampilkan hasil setiap kiriman, dan dari sana kamu bisa mengirim ulang event tertentu.
  • Periksa field livemode di 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.