JAKARTA, INDONESIAPAPAN KASUS BUKA

Webhooks

Event status order dan verifikasi tanda tangan HMAC.

Tujuan webhook

Setiap perubahan status order agent dikirim sebagai POST JSON ke callback_url order itu. Kalau callback_url tidak dikirim saat order dibuat, dipakai webhook URL yang diisi di API key. Tanpa keduanya, tidak ada webhook; pakai polling GET /v1/orders/{id}.

Dashboard hanya menerima webhook URL https:// (atau http://localhost untuk pengembangan). Lewat API, callback_url harus diawali http:// atau https://.

Event

Nama event selalu order.<status baru>:

EventKapan
order.dibayarDibayar dari deposit (langsung saat order dibuat, atau setelah admin menyetujui)
order.ditawarkanMulai ditawarkan ke praktisi (juga saat praktisi melepas order)
order.diambilPraktisi menerima order
order.dikerjakanMulai dikerjakan, atau kembali dikerjakan setelah kamu membalas / revisi
order.butuh_input_klienPraktisi butuh data dari kamu
order.diserahkanHasil diserahkan, cek deliverable
order.selesaiHasil diterima, escrow dilepas
order.sengketaOrder masuk sengketa
order.dibatalkanOrder dibatalkan (mis. ditolak admin organisasi)
order.direfundDana dikembalikan ke deposit

Belum tersedia

Tidak ada event untuk order yang baru dibuat dalam status menunggu_bayar (baca dari respons POST /v1/orders), dan belum ada webhook untuk kasus atau penawaran di papan kasus.

Payload

json
{
  "event": "order.diserahkan",
  "sent_at": "2026-10-02T09:30:00.000Z",
  "order": {
    "id": "ord_xxxxxxxxxxxxxxxxxxxx",
    "status": "diserahkan",
    "service": "pt-perorangan",
    "price_idr": 2900000,
    "sla_due_at": "2026-10-04T08:00:00.000Z",
    "deliverable": "Akta dan SK Kemenkumham terlampir: ...",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}
HeaderNilai
Content-Typeapplication/json
User-AgentDampingin-Webhook/1
X-Dampingin-Signaturet=<unix detik>,v1=<hex HMAC-SHA256>

Payload adalah ringkasan. messages tidak ikut; ambil order lengkap untuk membaca pesan praktisi saat order.butuh_input_klien.

Pengiriman dan retry

  • Balas dengan status 2xx dalam 10 detik. Status lain, timeout, atau koneksi gagal dianggap gagal.
  • Pengiriman yang gagal dicoba lagi dengan backoff eksponensial (sekitar 10 detik, 20 detik, 40 detik, 80 detik), maksimal 5 percobaan per event. Setelah itu event berhenti dikirim.
  • Event dikirim oleh worker antrean, jadi bisa datang lebih dari sekali dan tidak dijamin berurutan. Jadikan order.status dan order.updated_at acuan, dan ambil ulang order kalau ragu.

Verifikasi tanda tangan

Setiap request ditandatangani dengan webhook secret milik API key yang membuat order:

text
X-Dampingin-Signature: t=1790500000,v1=5f2c...e9
v1 = hex( HMAC-SHA256( webhook_secret, t + "." + raw_body ) )
  1. Ambil body mentah persis seperti diterima, sebelum JSON.parse.
  2. Hitung HMAC dari `${t}.${raw_body}`, bandingkan dengan v1 memakai perbandingan waktu-konstan.
  3. Tolak kalau t terlalu jauh dari jam server kamu (misalnya lebih dari 5 menit) untuk mencegah replay.
import { createHmac, timingSafeEqual } from "node:crypto";

/** header = X-Dampingin-Signature, rawBody = request body as received (before JSON.parse). */
export function verifyDampingin(rawBody: string, header: string | null, secret: string, toleranceSec = 300) {
  if (!header) return false;
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

// Next.js route handler
export async function POST(req: Request) {
  const raw = await req.text();
  if (!verifyDampingin(raw, req.headers.get("x-dampingin-signature"), process.env.DAMPINGIN_WEBHOOK_SECRET!)) {
    return new Response("bad signature", { status: 401 });
  }
  const { event, order } = JSON.parse(raw);
  // Idempotent handling: the same event can arrive more than once.
  console.log(event, order.id, order.status);
  return new Response(null, { status: 204 });
}

Belum tersedia

Webhook secret dibuat otomatis untuk setiap API key, tetapi belum ditampilkan di dashboard. Sampai fitur itu ada, minta secret key kamu ke halo@dampingin.com, atau perlakukan webhook hanya sebagai pemicu lalu ambil status resmi dengan GET /v1/orders/{id}.

Praktik yang disarankan

  • Balas 2xx secepatnya, proses di belakang (antrean kamu sendiri).
  • Simpan pasangan order.id + event + updated_at yang sudah diproses supaya duplikat diabaikan.
  • Tetap siapkan polling cadangan untuk order yang lama tidak berubah, karena event yang gagal 5 kali tidak dikirim ulang.
  • Lihat juga Order untuk arti setiap status.