Lewati ke konten utama

Dokumentasi · Integrasi

Integrasi API dan webhook

Cara menghubungkan HRIS, LMS, atau portal mitra: kunci API, izin, batas pemakaian, dan webhook bertanda tangan.

Diperbarui 2026-11-20

Halaman ini untuk tim teknis mitra: HRIS, LMS, ATS, atau portal perusahaan yang perlu mengirim peserta dan menerima hasil secara otomatis.

1. Yang perlu disiapkan

  • Satu klien integrasi dari tim kami. Klien adalah batas data: peserta, hasil, dan room yang dibuat lewat API hanya terlihat oleh klien itu.
  • Satu kunci API per lingkungan (produksi dan uji dipisah).
  • Satu endpoint HTTPS bila Anda ingin menerima event.

Kunci produksi berawalan sk_live_, kunci uji berawalan sk_test_. Keduanya hanya ditampilkan sekali saat dibuat.

2. Autentikasi

Setiap permintaan membawa header:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Kunci dipetakan ke klien, lalu diperiksa: status klien, masa berlaku, allowlist IP (bila diatur), izin, batas laju, dan kuota harian. Kegagalan izin dijawab 403; batas laju dan kuota dijawab 429.

3. Izin

Izin efektif adalah irisan antara izin klien dan izin kunci. Artinya mencabut izin di klien langsung mempersempit semua kunci di bawahnya.

IzinKegunaan
catalog:readDaftar instrumen yang terbit
participants:readDaftar dan detail peserta
participants:writeMembuat dan mengubah peserta
results:readDaftar dan detail hasil beserta skala
reports:readStatus laporan dan tautan PDF
orders:readPesanan dan status pembayaran
rooms:readDaftar dan detail room
rooms:writeMembuat room dan menugaskan tes
webhooks:manageMengelola endpoint webhook

4. Bentuk respons

Semua respons sukses memakai amplop data dan meta:

{
  "data": { "id": "…" },
  "meta": { "requestId": "req_…", "nextCursor": "…" }
}

Galat memakai amplop error:

{
  "error": {
"code": "validation_error",
"message": "Email peserta tidak sah.",
"requestId": "req_…"
  }
}

Sertakan requestId saat melapor; kami dapat melacak permintaan itu sampai ke log.

5. Paginasi

Daftar memakai cursor. Ambil meta.nextCursor dan kirim ulang sebagai ?cursor=. limit bawaan 25, maksimum 100. Jangan menyusun cursor sendiri: isinya dapat berubah.

6. Idempotensi

POST /api/v1/participants idempoten berdasarkan email. Mengirim ulang permintaan yang sama mengembalikan peserta yang sama, bukan duplikat. Ini membuat retry aman tanpa kunci idempotensi tambahan.

Bila email itu sudah terdaftar pada klien lain, permintaan ditolak 409. Kami tidak memindahkan data antar klien secara diam-diam.

7. Webhook

Event yang tersedia

EventKapan dikirim
attempt.startedPeserta mulai mengerjakan tes
attempt.submittedJawaban dikirim dan skor tersedia
commerce.order.paidPembayaran terkonfirmasi
report.reviewedLaporan ditinjau atau ditandatangani
result.invalidatedHasil dibatalkan karena integritas
result.reinstatedHasil dipulihkan

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

Bentuk payload

{
  "id": "dlv_…",
  "type": "attempt.submitted",
  "version": 1,
  "occurredAt": "2026-02-03T04:05:06.000Z",
  "data": { "resultId": "…", "participantId": "…", "band": "moderate" }
}

Payload adalah proyeksi: hanya field yang relevan untuk mitra. Jawaban mentah dan catatan klinis tidak pernah dikirim.

HeaderIsi
X-Sikotes-EventNama event
X-Sikotes-DeliveryID pengiriman, stabil antar percobaan
X-Sikotes-Signaturet=<unix>,v1=<hex hmac-sha256>
Idempotency-KeySama dengan ID pengiriman

Verifikasi tanda tangan

Tanda tangan dihitung atas ${t}.${rawBody} memakai rahasia endpoint. Verifikasi wajib memakai body mentah, bukan hasil parse ulang.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(
header.split(",").map((part) => part.split("=") as [string, string]),
  );
  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && timingSafeEqual(given, want);
}

Tolak permintaan di luar toleransi 5 menit, dan balas 2xx secepat mungkin. Pekerjaan berat sebaiknya diantrekan setelah balasan dikirim.

Retry

Pengiriman yang gagal diulang dengan backoff eksponensial hingga 8 percobaan. Setelah itu statusnya menjadi gagal permanen dan dapat dijadwalkan ulang dari konsol kami. Endpoint yang gagal 25 kali beruntun dinonaktifkan otomatis.

Pengiriman tidak dijamin berurutan. Gunakan occurredAt untuk mengurutkan dan id untuk mendeteksi duplikat.

8. Batas pemakaian

BatasBawaanDapat diubah
Permintaan per menit120Ya, sampai 6000
Kuota harianTanpa batasYa
Ukuran body16–64 KB per endpointTidak
Halaman daftar25 (maks 100)Ya, per permintaan

Saat menerima 429, hormati header Retry-After dan gunakan backoff dengan jitter.

9. Dokumen OpenAPI

Spesifikasi lengkap tersedia di `/api/v1/openapi.json`. Dokumen itu dihasilkan dari kode yang sama dengan implementasi, sehingga selalu sinkron. Impor ke Postman, Insomnia, atau generator klien favorit Anda.

Bila lebih nyaman membaca di peramban, dua halaman referensi di bawah ini dirender dari sumber yang sama — bukan salinan yang bisa basi:

  • Referensi API — setiap endpoint dengan izin, parameter, bentuk body, dan contoh panggilan.
  • Referensi webhook — enam event keluar, bentuk payload per event, header tanda tangan, cara verifikasi, dan kebijakan percobaan ulang.

10. Uji cepat

curl -s https://sikotes.id/api/v1/me \
  -H "Authorization: Bearer $SIKOTES_API_KEY"

Balasan memuat identitas klien, izin efektif, batas pemakaian, dan daftar event webhook yang tersedia. Bila balasan ini sudah benar, sisa integrasi biasanya hanya soal pemetaan data.