Lewati ke konten utama

Dokumentasi · Referensi

Referensi webhook

Enam event keluar, bentuk payload per event, header tanda tangan, cara verifikasi, dan kebijakan percobaan ulang.

6 eventHMAC-SHA256Toleransi 5 menit

Cara kerja singkat

  1. Buat endpoint di /admin/integrasi atau lewat POST /api/v1/webhooks. Rahasia tanda tangan ditampilkan sekali saat itu.
  2. Pilih event: nama persis, prefiks seperti attempt.*, atau * untuk semua.
  3. Balas 2xx secepat mungkin. Pekerjaan berat diantrekan setelah balasan dikirim; pengiriman yang gagal diulang otomatis.
  4. Verifikasi tanda tangan pada body mentah sebelum membaca JSON, lalu deduplikasi memakai Idempotency-Key.

Amplop event

Setiap pengiriman memakai bentuk yang sama. Isi data berbeda per event dan hanya memuat field yang relevan untuk mitra — jawaban mentah dan catatan klinis tidak pernah dikirim.

POST ke endpoint AndajsonReady
1{2  "id": "dlv_01HQ8Z3K4M5N6P7Q8R9S0T",3  "type": "attempt.submitted",4  "version": 1,5  "occurredAt": "2026-02-03T04:05:06.000Z",6  "data": {7    "sessionId": "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0",8    "resultId": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",9    "participantId": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80912",10    "instrumentSlug": "dass-21",11    "attemptNo": 1,12    "band": "moderate",13    "needsReview": false14  }15}

Event yang tersedia

Berlangganan memakai nama persis, prefiks seperti attempt.*, atau * untuk semua event.

attempt.started5 field

Peserta memulai sesi tes.

Payload attempt.started
FieldTipeKeterangan
sessionIdstringID sesi tes.
participantIdstringID peserta.
instrumentIdstringID instrumen.
instrumentSlugstringSlug instrumen, mis. `dass-21`.
proctoredboolean | nullApakah sesi diawasi.
attempt.submitted13 field

Sesi tes dikirim dan hasilnya tersimpan.

Payload attempt.submitted
FieldTipeKeterangan
sessionIdstringID sesi tes.
resultIdstringID hasil.
participantIdstringID peserta.
instrumentIdstringID instrumen.
instrumentSlugstringSlug instrumen.
instrumentNamestringNama instrumen.
attemptNonumber | nullUrutan percobaan peserta.
levelstringTingkat hasil menurut norma instrumen.
bandstring | nullKategori ringkas hasil.
riskLevelstring | nullPenanda risiko bila instrumen menyediakannya.
integrityScorenumber | nullSkor integritas sesi (0–100).
needsReviewboolean | nullPerlu tinjauan psikolog.
durationSecondsnumber | nullDurasi pengerjaan (detik).
commerce.order.paid5 field

Pesanan dinyatakan lunas dan hak akses diberikan.

Payload commerce.order.paid
FieldTipeKeterangan
orderIdstringID pesanan.
orderNumberstringNomor pesanan yang tercetak di invoice.
participantIdstringID peserta pemilik pesanan.
totalIdrnumber | nullTotal dibayar dalam rupiah.
providerstring | nullPenyedia pembayaran.
report.reviewed4 field

Psikolog menandatangani atau mencabut laporan hasil.

Payload report.reviewed
FieldTipeKeterangan
reportIdstringID laporan.
resultIdstringID hasil yang dilaporkan.
participantIdstringID peserta.
statusstringStatus laporan setelah tinjauan.
result.invalidated5 field

Staf menginvalidasi hasil tes.

Payload result.invalidated
FieldTipeKeterangan
resultIdstringID hasil.
sessionIdstringID sesi tes.
participantIdstringID peserta.
attemptNonumber | nullUrutan percobaan peserta.
reasonstring | nullAlasan invalidasi.
result.reinstated4 field

Hasil tes yang sebelumnya invalid dipulihkan.

Payload result.reinstated
FieldTipeKeterangan
resultIdstringID hasil.
sessionIdstringID sesi tes.
participantIdstringID peserta.
attemptNonumber | nullUrutan percobaan peserta.

Verifikasi tanda tangan

Tanda tangan dihitung atas ${t}.${rawBody} memakai rahasia endpoint. Verifikasi wajib memakai body mentah, bukan hasil parse ulang, dan perbandingan harus waktu-konstan.

verify.tstypescriptReady
1import { createHmac, timingSafeEqual } from "node:crypto";23const TOLERANCE_SECONDS = 300;45export function verify(rawBody: string, header: string, secret: string): boolean {6  const parts = Object.fromEntries(7    header.split(",").map((part) => part.split("=") as [string, string]),8  );9  const timestamp = Number(parts.t);10  if (!Number.isFinite(timestamp)) return false;11  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;1213  const expected = createHmac("sha256", secret)14    .update(`${parts.t}.${rawBody}`)15    .digest("hex");1617  const given = Buffer.from(parts.v1 ?? "", "hex");18  const want = Buffer.from(expected, "hex");19  return given.length === want.length && timingSafeEqual(given, want);20}

Percobaan ulang

Kebijakan percobaan ulang webhook
AspekNilaiKeterangan
Percobaan maksimum8Setelah itu status pengiriman menjadi gagal permanen dan dapat dijadwalkan ulang dari konsol.
Jeda antar percobaanEksponensial, 5 detik → 1 jamDitambah jitter per pengiriman agar endpoint tidak menerima gelombang serentak.
Batas waktu satu permintaan10 detikBalas `2xx` lebih cepat dari itu; pekerjaan berat diantrekan setelah balasan.
Nonaktif otomatis25 kegagalan beruntunEndpoint dinonaktifkan dan ditandai di konsol; mengaktifkan kembali mereset penghitung.
UrutanTidak dijaminUrutkan memakai `occurredAt`, dan deteksi duplikat memakai `id`.