Webhook
Kabari sistem kamu otomatis setiap ada pesanan, pendaftaran, pembatalan, atau check-in.
Dengan webhook, Nyebar mengirim kabar ke sistem kamu begitu sesuatu terjadi, misalnya untuk memasukkan pembeli ke CRM, mengirim pesan WhatsApp sambutan dari sistem sendiri, atau memperbarui papan skor kehadiran. Sistem kamu tidak perlu bertanya terus ke API.
Webhook diatur oleh owner atau Admin di menu Integrasi. Bagian akhir artikel ini ditujukan untuk developer yang menyiapkan penerimanya.
Kejadian yang bisa dikirim
| Kejadian | Kapan dikirim |
|---|---|
Pesanan terkonfirmasi (order.confirmed) | Pesanan dibayar (atau gratis) dan tiketnya terbit. Termasuk pendaftaran gratis yang kamu setujui. |
Pendaftaran masuk (registration.received) | Pendaftaran baru menunggu persetujuan kamu. |
Pesanan batal (order.cancelled) | Pendaftaran ditolak, atau pesanan kedaluwarsa atau gagal dibayar. |
Tiket check-in (ticket.checked_in) | Tiket di-check-in, dari dasbor, link kru, maupun API. |
Menambah webhook
-
Buka menu Integrasi, lalu di bagian Webhook klik Tambah webhook.
Daftar webhook di halaman Integrasi. -
Isi URL endpoint, yaitu alamat di sistem kamu yang menerima kiriman. Alamatnya harus https dan bisa diakses dari internet.
-
Pilih Kejadian yang dikirim, lalu klik Simpan.
Isi URL dan pilih kejadian yang ingin dikirim. -
Salin secret yang muncul dan pasang di sistem kamu untuk memverifikasi kiriman. Secret juga bisa dilihat lagi di halaman webhook.
Satu organizer bisa punya sampai 5 webhook.
Memantau kiriman
Di daftar webhook, setiap baris punya dua ikon:
- Riwayat kiriman (ikon jam): kiriman 30 hari terakhir. Klik satu kiriman untuk melihat isi kiriman dan respons dari sistem kamu, lalu Kirim ulang kalau perlu.
- Pengaturan (ikon roda gigi): mengganti URL, deskripsi, atau kejadian lewat Edit Endpoint, menyalakan atau mematikan webhook sementara, melihat atau mengganti Secret penandatanganan, dan menghapus webhook.
Di bagian atas kedua halaman ada Kirim tes, untuk memastikan endpoint kamu menerima kiriman dengan benar.
Kalau sistem kamu tidak menjawab dengan sukses, Nyebar mencoba lagi otomatis setelah 1 menit, 5 menit, 30 menit, 2 jam, 6 jam, dan 24 jam. Kalau kiriman ke satu webhook terus gagal selama lebih dari sehari, webhook dimatikan otomatis dan owner serta Admin mendapat notifikasi di dasbor. Setelah endpoint diperbaiki, kirim tes lalu nyalakan lagi. Kejadian yang terjadi selama webhook mati tetap dikirim setelah dinyalakan, selama belum lewat 30 hari.
Untuk developer
Format kiriman
Nyebar mengirim POST berisi JSON ke URL kamu, dengan header:
| Header | Isi |
|---|---|
Nyebar-Event-Id | ID kejadian, misalnya evt_1042. Sama untuk setiap percobaan ulang. |
Nyebar-Event-Type | Jenis kejadian, misalnya order.confirmed. |
Nyebar-Signature | Tanda tangan, t=<waktu unix>,v1=<hex>. |
{
"id": "evt_1042",
"type": "order.confirmed",
"created_at": "2026-10-11T14:36:46Z",
"data": {
"event": { "id": 134, "title": "Nusantara City Run 10K", "slug": "nusantara-city-run-10k" },
"order": {
"id": 1230,
"code": "ORDRHMRQERV",
"status": "confirmed",
"buyer_name": "Andi Nasution",
"buyer_email": "andi@contoh.com",
"ticket_subtotal": 200000,
"addon_subtotal": 0,
"discount_amount": 0,
"platform_fee_amount": 0,
"total_price": 200000,
"created_at": "2026-10-11T14:30:02Z"
},
"tickets": [
{
"id": 1583,
"code": "NYE8KRXGKU8",
"holder_name": "Andi Nasution",
"holder_email": "andi@contoh.com",
"ticket_type": { "id": 235, "name": "Reguler" },
"bib_number": null,
"checked_in_at": null,
"created_at": "2026-10-11T14:36:46Z"
}
]
}
}
Isi data per kejadian:
order.confirmed:event,order, danticketsyang terbit.registration.received:eventdanorder(statusawaiting_approval).order.cancelled:event,order, danreason:rejected(ditolak),expired(batas waktu persetujuan atau pembayaran lewat),payment_failed, ataupayment_expired.ticket.checked_in:event,ticket, dansource:dashboard,crew_link,api, atausupport.
Bentuk order dan ticket sama dengan respons API organizer.
Menjawab kiriman
Jawab dengan status 2xx dalam 10 detik. Kalau sistem kamu butuh waktu lebih lama, simpan kirimannya dulu, jawab 200, lalu proses belakangan. Status selain 2xx, timeout, atau redirect dianggap gagal dan dicoba lagi.
Kiriman bisa datang lebih dari sekali (misalnya saat percobaan ulang) dan urutannya tidak dijamin. Gunakan id kejadian untuk melewati kiriman yang sudah pernah diproses, dan created_at untuk urutannya.
Memverifikasi tanda tangan
Hitung HMAC-SHA256 dari <t>.<body mentah> dengan secret webhook, lalu bandingkan dengan nilai v1. Tolak juga kiriman yang t-nya lebih dari 5 menit dari waktu sekarang.
// Node.js (Express): pakai body mentah, bukan hasil JSON.parse
const crypto = require("crypto");
app.post("/webhooks/nyebar", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Nyebar-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", process.env.NYEBAR_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!fresh || !parts.v1 || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// ... proses event ...
res.sendStatus(200);
});
<?php
// PHP
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_NYEBAR_SIGNATURE'] ?? ''), $parts);
$expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $body, getenv('NYEBAR_WEBHOOK_SECRET'));
if (abs(time() - (int) ($parts['t'] ?? 0)) > 300 || !hash_equals($expected, $parts['v1'] ?? '')) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
// ... proses event ...
http_response_code(200);
# Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhooks/nyebar")
def nyebar_webhook():
parts = dict(p.split("=", 1) for p in request.headers.get("Nyebar-Signature", "").split(",") if "=" in p)
body = request.get_data()
expected = hmac.new(os.environ["NYEBAR_WEBHOOK_SECRET"].encode(), parts.get("t", "").encode() + b"." + body, hashlib.sha256).hexdigest()
if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
abort(400)
event = request.get_json()
# ... proses event ...
return "", 200
Kalau secret diganti lewat Ganti secret, secret lama langsung tidak berlaku. Perbarui secret di sistem kamu segera setelahnya.
Apakah artikel ini membantu?
Terima kasih atas masukanmu!
Terima kasih. Kami akan memperbaiki artikel ini.
Masih butuh bantuan?
Tim Nyebar siap membantu lewat WhatsApp atau email. Cara lain menghubungi kami