Menerima webhook
Webhook memberi tahu sistem Anda tentang perubahan di Sursa saat perubahan itu terjadi: kartu dipublikasikan,
diverifikasi, atau diarsipkan, pertanyaan tidak terjawab. Sursa mengirim POST HTTPS berisi peristiwa ke alamat yang
Anda daftarkan. Panduan ini membangun penerima yang tetap benar saat terjadi percobaan ulang, perubahan urutan, dan
duplikat.
Untuk arti dan isi setiap peristiwa, lihat ikhtisar webhook dan katalog peristiwa di referensi API.
1. Daftarkan webhook
Administrator mendaftarkan webhook di Sursa melalui Administration → Integrations → Webhooks and API
(Administrasi → Integrasi → Webhook dan API), atau dengan POST /v1/webhooks dan
kunci dengan peran admin dan cakupan webhooks:manage:
curl -X POST https://api.sursa.com/v1/webhooks \
-H "Authorization: Bearer $SURSA_ADMIN_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/sursa",
"description": "Sync verified cards to the help center",
"events": ["card.published", "card.updated", "card.verified", "card.archived"]
}'Respons berisi rahasia penandatanganan di secret, diawali whsec_. Rahasia ini ditampilkan sekali. Simpan
bersama penerima Anda, misalnya di variabel lingkungan SURSA_WEBHOOK_SECRET.
Alamat harus memakai https dan mengarah ke alamat IP publik: alamat di jaringan privat, loopback, dan jaringan
khusus lainnya ditolak saat Anda mendaftarkannya dan pada setiap pengiriman. Untuk mengembangkan di komputer Anda
sendiri, buka akses ke penerima lokal Anda melalui layanan tunneling yang memberinya alamat HTTPS publik.
Kirim peristiwa uji kapan saja dengan POST /v1/webhooks/{webhook_id}/test.
Respons menunjukkan apa yang dijawab penerima Anda.
2. Seperti apa sebuah pengiriman
POST /sursa HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: evt_0123456789abcdef0123456789abcdef
webhook-timestamp: 1791194400
webhook-signature: v1,l1LLl/aEl4dQq7Geb383KbyNeZgs7VZpiw6iuoJgunI=
X-KB-Signature: t=1791194400,v1=82aa6b27378d89d7d880d17c3fe96268598443ae0e64d059c0c41e94e5d3ffbe
X-KB-Event-Id: evt_0123456789abcdef0123456789abcdef
X-KB-Event: card.verified
X-KB-Delivery: 99812
X-KB-Attempt: 1
X-KB-Sequence: 1042
{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"api_version":"1.60.0","sequence":1042,"object":{"type":"card","id":"c1"},"payload":"full","data":{"card":{"id":"c1"}},"test":false}
| Header | Artinya |
|---|---|
webhook-id | ID peristiwa. Sama di setiap percobaan ulang. Pakai untuk membuang duplikat. |
webhook-timestamp | Kapan upaya ini ditandatangani, dalam detik Unix. |
webhook-signature | Tanda tangan dalam format Standard Webhooks. |
X-KB-Signature | Pengiriman yang sama, ditandatangani dalam format Sursa yang lebih lama. |
X-KB-Event | Jenis peristiwa. |
X-KB-Attempt | Nomor upaya, mulai dari 1. |
X-KB-Sequence | Posisi pengiriman ini dalam urutan webhook. |
Nama header tidak peka huruf besar-kecil; framework Anda mungkin menampilkannya sebagai X-Kb-Signature atau
x-kb-signature.
Isinya adalah peristiwa. data-nya bergantung pada jenis peristiwa. Webhook yang diatur ke payload thin menerima
peristiwa tanpa data; sebagai gantinya, baca objek melalui API dengan kunci Anda sendiri.
3. Periksa tanda tangan
Periksa tanda tangan setiap pengiriman sebelum Anda memercayainya. Kedua format menandatangani byte isi yang persis, jadi baca raw body sebelum penguraian JSON apa pun: menyerialisasi ulang JSON mengubah byte-nya dan menggagalkan pemeriksaan.
| Format | String yang ditandatangani | Kunci | Tanda tangan |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Bagian rahasia setelah whsec_, didekode dari base64 | v1, + base64 dari HMAC-SHA256 |
Format lama (X-KB-Signature) | <t>.<body> | Seluruh string rahasia, sebagai teks | v1= + hex dari HMAC-SHA256 |
Memeriksa salah satunya sudah cukup. Utamakan header Standard Webhooks: pemeriksaan yang sama tersedia di banyak
pustaka siap pakai. Selama rotasi rahasia, setiap header membawa satu tanda tangan per rahasia yang berlaku (dipisahkan
spasi di webhook-signature, v1= berulang di X-KB-Signature). Pengiriman valid jika salah satunya cocok.
Tolak juga timestamp yang berselisih lebih dari 5 menit dari jam Anda. Ini mencegah seseorang memutar ulang pengiriman yang direkam di kemudian hari.
# Check a saved delivery by hand: body.json holds the raw body, the values come from its headers.
SECRET="whsec_…"; WEBHOOK_ID="evt_…"; TIMESTAMP="1791194400"
# Standard Webhooks: compare with the value after "v1," in webhook-signature
KEY=$(printf '%s' "${SECRET#whsec_}" | tr '_-' '/+')
while [ $(( ${#KEY} % 4 )) -ne 0 ]; do KEY="$KEY="; done
KEY_HEX=$(printf '%s' "$KEY" | openssl base64 -d -A | xxd -p | tr -d '\n')
{ printf '%s.%s.' "$WEBHOOK_ID" "$TIMESTAMP"; cat body.json; } \
| openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX" -binary | openssl base64 -A
# Earlier format: compare with the value after "v1=" in X-KB-Signature
{ printf '%s.' "$TIMESTAMP"; cat body.json; } | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1Uji kode pemeriksa Anda
Jalankan kode Anda dengan nilai-nilai ini sebelum menghubungkannya ke pengiriman sungguhan. Dengan jam disetel ke timestamp tersebut, kedua pemeriksaan harus lolos, dan harus gagal jika satu byte isi berubah.
| Masukan | Nilai |
|---|---|
| Rahasia | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp dan t | 1791194400 |
| Isi | {"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}} |
webhook-signature | v1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg= |
X-KB-Signature | t=1791194400,v1=ce26a19cb63cd3ae620217bf7688e1df1044fa7d59d9ae148d215f0292b768dc |
4. Jawab dengan cepat, kerjakan belakangan
Sursa menunggu jawaban Anda hingga 10 detik. Status 2xx apa pun berarti pengiriman berhasil; selain itu,
termasuk pengalihan, timeout, atau koneksi yang ditutup, berarti pengiriman gagal dan akan dicoba ulang.
Jadi penerima sebaiknya hanya memeriksa, mencatat peristiwa, dan menjawab. Kerjakan pekerjaan sebenarnya sesudahnya, dari antrean Anda sendiri.
// Node.js 20+, no framework. verifyStandard and verifyLegacy come from step 3.
import http from "node:http";
const SECRET = process.env.SURSA_WEBHOOK_SECRET;
http
.createServer(async (req, res) => {
const parts = [];
for await (const part of req) parts.push(part);
const body = Buffer.concat(parts); // the raw bytes, before any JSON parsing
const valid = req.headers["webhook-signature"]
? verifyStandard(SECRET, req.headers, body)
: verifyLegacy(SECRET, req.headers["x-kb-signature"], body);
if (!valid) {
res.writeHead(400).end();
return;
}
const event = JSON.parse(body);
await saveEventOnce(event); // insert into your table or queue, keyed by event.id; ignore a duplicate
res.writeHead(204).end();
})
.listen(3000);Jawab 2xx hanya setelah peristiwa tersimpan dengan aman. Jika Anda menjawab lebih dulu lalu sistem Anda mogok,
peristiwa itu hilang bagi Anda, karena Sursa sudah menganggapnya terkirim.
5. Percobaan ulang
Pengiriman yang gagal dicoba ulang dengan jeda yang makin panjang: setelah sekitar 1 menit, 5 menit, 30 menit, 2 jam, 5 jam, 10 jam, 24 jam, dan 24 jam, sembilan upaya selama hampir tiga hari. Setiap jeda bervariasi hingga 20% agar percobaan ulang dari banyak webhook tidak tiba pada saat yang sama.
- Jawaban
429atau503denganRetry-Aftermemundurkan upaya berikutnya setidaknya hingga waktu itu, paling lambat 24 jam kemudian. Pakai ini saat sistem Anda kelebihan beban atau sedang dalam pemeliharaan. - Jawaban
410 Gonemenonaktifkan webhook: kirim jawaban ini hanya jika Anda tidak menginginkan pengiriman lagi ke alamat itu. - Setelah 10 upaya gagal berturut-turut, webhook dijeda, dan administrator organisasi diberi tahu. Peristiwa terus
terkumpul, dan saat webhook dilanjutkan kembali, di antarmuka administrasi atau dengan
POST /v1/webhooks/{webhook_id}/resume, peristiwa itu dikirim sesuai urutan. - Setelah upaya terakhir, pengiriman ditandai
exhausted. Anda dapat mengirimnya lagi secara manual.
Pengiriman sebuah webhook, beserta setiap upaya, kode jawaban Anda, dan waktunya, dicantumkan oleh
GET /v1/webhooks/{webhook_id}/deliveries dan di antarmuka
administrasi. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
mengirim satu pengiriman lagi. Pengiriman ulang memiliki webhook-id yang sama.
6. Urutan dan duplikat
- Urutan. Pengiriman ke satu webhook berjalan ketat satu demi satu sesuai urutan
sequence. Selama satu pengiriman dicoba ulang, pengiriman berikutnya menunggu. Webhook yang berbeda tidak saling menunggu. - Duplikat. Peristiwa yang sama dapat tiba lebih dari sekali, misalnya ketika jawaban Anda hilang dalam perjalanan
kembali. Simpan
webhook-id(sama denganiddi isi) setiap peristiwa yang Anda proses, dan lewati peristiwa yang sudah pernah Anda lihat. - Berpikir dalam keadaan objek. Untuk perubahan pada objek yang sama, bandingkan
created_atatausequencedengan yang Anda simpan, dan abaikan peristiwa yang lebih lama daripada keadaan yang sudah Anda miliki. Jika ragu, baca objek terkini melalui API: peristiwa memberi tahu Anda bahwa sesuatu berubah, API memberi tahu Anda bagaimana keadaannya sekarang.
7. Lakukan rekonsiliasi dengan umpan peristiwa
Setiap peristiwa juga disimpan selama 30 hari di umpan peristiwa, terlepas dari ada atau tidaknya webhook yang
berlangganan peristiwa itu. Jika penerima Anda mati lebih lama daripada rentang percobaan ulang, atau Anda ingin
memeriksa bahwa tidak ada yang terlewat, baca umpannya dengan kunci yang memiliki peran admin dan cakupan
webhooks:manage:
curl -G https://api.sursa.com/v1/webhook-events \
-H "Authorization: Bearer $SURSA_API_KEY" \
--data-urlencode "type=card.*" \
--data-urlencode "created_since=2026-10-20T00:00:00Z" \
--data-urlencode "limit=200"Peristiwa di umpan memiliki id dan bentuk yang sama seperti dalam pengiriman, tanpa field pengiriman sequence,
payload, dan test. Karena penerima Anda melewati peristiwa yang sudah pernah dilihatnya, Anda dapat menjalankan
rekonsiliasi sesering yang Anda mau, misalnya sekali per jam.
GET /v1/webhook-events/{event_id} membaca satu peristiwa menurut ID-nya.
8. Rotasi rahasia
POST /v1/webhooks/{webhook_id}/rotate-secret menerbitkan rahasia baru
dan membiarkan rahasia sebelumnya tetap berlaku selama masa tumpang tindih, secara default 24 jam dan paling lama
7 hari. Selama masa tumpang tindih, setiap pengiriman ditandatangani dengan kedua rahasia.
- Lakukan rotasi, dan simpan rahasia baru di samping rahasia lama di penerima Anda.
- Terima pengiriman jika lolos pemeriksaan dengan salah satu rahasia.
- Setelah masa tumpang tindih, hapus rahasia lama.
Jika rahasia lama bocor, segera akhiri masa tumpang tindih dengan
POST /v1/webhooks/{webhook_id}/revoke-previous-secret, atau
rotasi rahasia dengan "overlap_hours": 0.
Daftar periksa
- Periksa salah satu tanda tangan pada raw body, dan periksa timestamp-nya.
- Simpan peristiwa dengan kunci
webhook-id, lalu jawab2xxdalam 10 detik. - Lewati duplikat dan peristiwa yang sudah kedaluwarsa; baca objek terkini melalui API jika hal itu penting.
- Jawab
429atau503denganRetry-Aftersaat Anda membutuhkan Sursa untuk menahan diri; jangan pernah menjawab410secara tidak sengaja. - Lewati jenis peristiwa dan field yang tidak Anda kenal.
- Lakukan rekonsiliasi dengan umpan peristiwa secara terjadwal.