Lewati ke konten
Dukungan

Webhook

Webhook memungkinkan Anda menerima notifikasi waktu nyata saat ada peristiwa yang terjadi di akun LabelGrid Anda. Gunakan webhook untuk mengotomatiskan alur kerja dan berintegrasi dengan sistem eksternal.

Untuk Pengembang: Anda juga dapat mengelola webhook secara terprogram melalui API. Lihat Dokumentasi API LabelGrid untuk endpoint dan contohnya.

  1. Anda mengonfigurasi webhook - Tentukan URL dan peristiwa apa saja yang ingin didengarkan
  2. Sebuah peristiwa terjadi - Misalnya, sebuah rilis dikirimkan ke toko
  3. LabelGrid mengirim permintaan POST - Server Anda menerima data peristiwa tersebut
  4. Sistem Anda memprosesnya - Otomatiskan alur kerja berdasarkan peristiwa tersebut

  1. Klik ikon profil Anda di pojok kanan atas
  2. Pilih Webhooks dari menu turun

  1. Klik Create Webhook
  2. Masukkan Name untuk menandai webhook ini
  3. Masukkan URL tempat Anda ingin menerima notifikasi
  4. Pilih Events mana yang akan memicu webhook ini
  5. Klik Create

Saat membuat webhook, Anda akan menerima sebuah secret key. Gunakan ini untuk memverifikasi bahwa permintaan yang masuk benar-benar berasal dari LabelGrid:

  • Simpan secret dengan aman
  • Verifikasi signature pada permintaan yang masuk
  • Jika bocor, buat ulang secret-nya

Konfigurasikan webhook Anda untuk mendengarkan peristiwa berikut. Event Identifier adalah nilai yang akan Anda lihat pada properti event di payload dan pada header X-Webhook-Event:

Event IdentifierDeskripsi
delivery.completedDipicu saat sebuah rilis berhasil dikirimkan ke sebuah outlet
delivery.failedDipicu saat pengiriman ke sebuah outlet gagal
takedown.completedDipicu saat permintaan takedown selesai
release.review.status_changedDipicu saat status peninjauan sebuah rilis berubah
release.preflight.report_readyDipicu saat laporan Preflight QC untuk rilis yang ditahan siap diambil
stream_radar.flag_createdDipicu saat sebuah tanda Stream Radar dimunculkan, atau tanda yang telah terselesaikan terbuka kembali pada deteksi baru
stream_radar.flag_resolvedDipicu saat sebuah tanda Stream Radar terselesaikan karena deteksi berhenti
release.distributedDipicu saat sebuah rilis didistribusikan
payment.statement_readyDipicu saat laporan pembayaran siap untuk dilihat
transcode.completedDipicu saat transcode audio sebuah track selesai dengan sukses
transcode.failedDipicu saat transcode sebuah track gagal atau berakhir tidak lengkap
distribution.outlet.status_changedDipicu pada setiap transisi status distribusi per outlet

Anda dapat memilih beberapa peristiwa untuk satu webhook, atau membuat webhook terpisah untuk jenis peristiwa yang berbeda.

Anda juga dapat membaca daftar ini secara terprogram: GET /api/public/webhook/event-types mengembalikan setiap peristiwa beserta skema data yang menjelaskan kunci dan tipe payload-nya, sehingga konsumen dengan skema ketat dapat memperluas validasi masukannya lebih awal.


Daftar webhook menampilkan:

KolomDeskripsi
NameNama webhook yang Anda tetapkan
URLTempat notifikasi dikirim
EventsJumlah peristiwa yang dikonfigurasi
StatusActive atau Inactive
Success / FailJumlah pengiriman yang berhasil dan gagal
Last TriggeredKapan webhook terakhir kali dipanggil
  1. Klik tindakan Edit pada baris webhook
  2. Ubah nama, URL, atau peristiwa
  3. Klik Save

Alihkan status aktif webhook tanpa menghapusnya:

  • Active - Webhook akan menerima notifikasi
  • Inactive - Webhook dijeda, tidak ada notifikasi yang dikirim
  1. Klik tindakan Delete pada baris webhook
  2. Konfirmasikan penghapusan

Sebelum mengandalkan sebuah webhook di produksi, ujilah terlebih dahulu:

  1. Klik tindakan Test pada webhook Anda
  2. LabelGrid mengirimkan payload uji ke URL Anda
  3. Pastikan endpoint Anda menerima dan memprosesnya dengan benar

Pantau aktivitas webhook dan atasi masalah:

  1. Klik tindakan View Logs pada sebuah webhook
  2. Lihat riwayat seluruh pengiriman webhook

Setiap entri log menampilkan:

FieldDeskripsi
Event TypePeristiwa apa yang memicu pengiriman ini
Response StatusKode status HTTP dari server Anda
DurationBerapa lama permintaan tersebut berlangsung
AttemptNomor percobaan ulang
TimestampKapan pengiriman terjadi

Saat sebuah peristiwa terjadi, LabelGrid mengirim permintaan POST ke URL Anda dengan payload JSON:

{
"event": "delivery.completed",
"timestamp": "2026-05-05T10:00:00+00:00",
"webhook_id": "123",
"data": {
// Event-specific data
}
}

Field timestamp menggunakan format ISO 8601. webhook_id adalah ID webhook yang Anda konfigurasi (nilainya cocok dengan header X-Webhook-Id).


Struktur objek data bergantung pada jenis event. Semua tipe field di bawah ini adalah tipe JSON sebagaimana diserialisasikan dalam payload.

Dipicu satu kali per outlet saat pengiriman sebuah rilis mencapai status keberhasilan akhir.

{
"event": "delivery.completed",
"timestamp": "2026-05-18T10:00:00+00:00",
"webhook_id": "123",
"data": {
"distro_queue_id": 456,
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"outlet_id": 12,
"outlet_name": "Spotify",
"status": "complete"
}
}
FieldTipeDeskripsi
distro_queue_idintegerID antrean internal untuk percobaan pengiriman ini
release_idintegerRilis yang dikirimkan
label_idintegerLabel pemilik rilis, sehingga Anda dapat mengarahkan peristiwa tanpa pencarian lanjutan
release_catstring | nullReferensi katalog rilis Anda
outlet_idinteger | nullID outlet tujuan
outlet_namestring | nullNama outlet yang mudah dibaca (mis. "Spotify")
statusstringSelalu "complete" untuk peristiwa ini

Dipicu satu kali per outlet saat pengiriman sebuah rilis mencapai status kegagalan akhir. Payload-nya sama dengan delivery.completed ditambah field message.

{
"event": "delivery.failed",
"timestamp": "2026-05-18T10:00:00+00:00",
"webhook_id": "123",
"data": {
"distro_queue_id": 456,
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"outlet_id": 12,
"outlet_name": "Spotify",
"status": "error",
"message": "Outlet rejected the delivery: missing ISRC."
}
}
FieldTipeDeskripsi
statusstringSalah satu dari error, fault, rejected, batch_exception
messagestring | nullAlasan kegagalan dari outlet atau pipeline distribusi

Dipicu satu kali per outlet saat permintaan takedown berhasil. Bentuknya sama dengan delivery.completed ditambah flag takedown: true.

{
"event": "takedown.completed",
"timestamp": "2026-05-18T10:00:00+00:00",
"webhook_id": "123",
"data": {
"distro_queue_id": 456,
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"outlet_id": 12,
"outlet_name": "Spotify",
"status": "complete",
"takedown": true
}
}

Dipicu satu kali per rilis saat rilis beralih ke status pengiriman distributed. Hanya dipicu pada saat peralihan menjadi distributed, bukan pada penyimpanan berikutnya selama rilis sudah berstatus distributed.

{
"event": "release.distributed",
"timestamp": "2026-05-18T10:00:00+00:00",
"webhook_id": "123",
"data": {
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"release_title": "Summer EP",
"delivery_status": "distributed"
}
}

Dipicu setiap kali sebuah rilis berpindah antar status peninjauan.

{
"event": "release.review.status_changed",
"timestamp": "2026-05-18T10:00:00+00:00",
"webhook_id": "123",
"data": {
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"release_title": "Summer EP",
"previous_status": "to_review",
"new_status": "approved"
}
}
FieldTipeDeskripsi
previous_statusstringStatus sebelumnya. Salah satu dari draft, to_review, approved, rejected, require_changes, audit
new_statusstringStatus baru. Kumpulan nilai yang sama
review_issuesarray (opsional)Hadir hanya pada transisi require_changes dan rejected: masalah yang memerlukan perhatian Anda. Kunci ini dihilangkan pada setiap transisi lain, jadi jangan anggap selalu ada

Dipicu saat laporan kualitas Preflight QC untuk sebuah rilis yang berada dalam penahanan pra-tinjauan siap diambil. Membutuhkan pengaya Preflight QC pada akun Anda.

{
"event": "release.preflight.report_ready",
"timestamp": "2026-07-07T10:00:00+00:00",
"webhook_id": "123",
"data": {
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"release_title": "Summer EP",
"generated_at": "2026-07-07T09:58:12+00:00",
"profile": { "name": "quality_report", "version": 2 },
"counts": { "blocking": 1, "informational": 2, "requires_feedback": 1 }
}
}
FieldTipeDeskripsi
release_idintegerRilis tempat laporan ini berada
label_idintegerLabel pemilik rilis
release_catstring | nullReferensi katalog rilis Anda
release_titlestring | nullJudul rilis
generated_atstringKapan pemeriksaan selesai (ISO 8601). Cocok dengan report.generated_at pada endpoint laporan kualitas
profileobjectProfil kualitas yang digunakan untuk menghitung jumlah: {name, version}
countsobjectHanya jumlah agregat: {blocking, informational, requires_feedback}. Jumlah requires_feedback tumpang tindih dengan dua lainnya

Dipicu saat sebuah tanda Stream Radar dimunculkan — baik sebuah tanda yang benar-benar baru maupun tanda yang sebelumnya terselesaikan yang terbuka kembali pada deteksi baru. Membutuhkan pengaya Stream Radar pada akun Anda. Field transition membedakan kedua kasus: published untuk tanda baru, reopened untuk tanda yang menjadi aktif kembali.

{
"event": "stream_radar.flag_created",
"timestamp": "2026-07-07T10:00:00+00:00",
"webhook_id": "123",
"data": {
"flag_id": 4501,
"dsp": "spotify",
"isrc": "USRC12345678",
"release_id": 789,
"track_id": 654,
"severity": "high",
"status": "active",
"transition": "published",
"first_detected_at": "2026-07-06T00:00:00+00:00",
"last_detected_at": "2026-07-07T00:00:00+00:00",
"estimated_affected_streams": 12500,
"published_at": "2026-07-07T09:58:12+00:00",
"resolved_at": null
}
}
FieldTipeDeskripsi
flag_idintegerPengidentifikasi tanda yang stabil; cocok dengan id pada endpoint Stream Radar
dspstringPlatform tempat pola terlihat (mis. spotify)
isrcstringISRC rekaman yang terlibat
release_idintegerRilis tempat rekaman itu berada
track_idinteger | nullTrek spesifik, ketika ISRC memetakan secara jelas ke salah satu trek Anda
severitystringlow, medium, atau high
statusstringactive untuk peristiwa ini
transitionstringpublished untuk tanda baru, reopened ketika tanda yang terselesaikan menjadi aktif kembali
first_detected_atstring | nullKapan pola pertama kali terlihat untuk trek dan platform ini (ISO 8601)
last_detected_atstring | nullDeteksi terbaru (ISO 8601)
estimated_affected_streamsinteger | nullPerkiraan berapa banyak stream yang terlibat
published_atstringKapan tanda pertama kali dimunculkan kepada Anda (ISO 8601)
resolved_atstring | nullnull selama tanda aktif

Dipicu saat sebuah tanda Stream Radar terselesaikan karena deteksi berhenti. Membutuhkan pengaya Stream Radar. Field-nya sama seperti stream_radar.flag_created (tanpa transition), dengan status yang bernilai resolved dan resolved_at yang terisi.

{
"event": "stream_radar.flag_resolved",
"timestamp": "2026-07-14T10:00:00+00:00",
"webhook_id": "123",
"data": {
"flag_id": 4501,
"dsp": "spotify",
"isrc": "USRC12345678",
"release_id": 789,
"track_id": 654,
"severity": "high",
"status": "resolved",
"first_detected_at": "2026-07-06T00:00:00+00:00",
"last_detected_at": "2026-07-12T00:00:00+00:00",
"estimated_affected_streams": 18700,
"published_at": "2026-07-07T09:58:12+00:00",
"resolved_at": "2026-07-14T09:55:03+00:00"
}
}

Dipicu saat laporan pembayaran dibuat dan siap untuk dilihat.

{
"event": "payment.statement_ready",
"timestamp": "2026-05-18T10:00:00+00:00",
"webhook_id": "123",
"data": {
"payment_request_id": 1024,
"invoice_number": "INV-2026-001",
"period": "2026-04-30",
"amount": 1234.56,
"total_due_usd": 1234.56,
"currency": "USD"
}
}
FieldTipeDeskripsi
payment_request_idintegerID permintaan pembayaran internal
invoice_numberstringReferensi faktur untuk laporan tersebut
periodstring | nullTanggal akhir periode (tanggal ISO 8601, YYYY-MM-DD)
amountnumberJumlah laporan dalam currency
total_due_usdnumberTotal laporan yang dikonversi ke USD
currencystringKode mata uang ISO 4217 (default-nya USD)

Dipicu saat transcode audio sebuah track selesai. transcode.completed dipicu saat berhasil; transcode.failed dipicu saat transcode gagal atau berakhir tidak lengkap. Keduanya memiliki bentuk payload yang sama.

{
"event": "transcode.completed",
"timestamp": "2026-07-07T10:00:00+00:00",
"webhook_id": "123",
"data": {
"release_id": 789,
"label_id": 321,
"track_id": 654,
"transcoder_queue_id": 987,
"status": "complete",
"status_message": "transcode_complete",
"files": [
{ "asset_type_id": 2, "status": "complete" }
]
}
}
FieldTipeDeskripsi
release_idintegerRilis tempat track ini berada
label_idintegerLabel pemilik rilis
track_idintegerTrack yang ditranskode
transcoder_queue_idintegerID antrean transcode internal
statusstringStatus antrean mentah: complete, error, atau incomplete
status_messagestringKode alasan yang aman dan terenumerasi: transcode_complete, transcode_error, atau transcode_incomplete
filesarrayDetail per berkas untuk track: {asset_type_id, status} per berkas yang ditranskode

Dipicu pada setiap transisi status distribusi per outlet (misalnya scheduled → transcoding → batched → complete), bukan hanya transisi akhir yang dicakup oleh delivery.completed, delivery.failed, dan takedown.completed. Peristiwa ini memang dirancang untuk sering terpicu: berlangganan hanya jika Anda menginginkan seluruh progres per outlet.

{
"event": "distribution.outlet.status_changed",
"timestamp": "2026-07-07T10:00:00+00:00",
"webhook_id": "123",
"data": {
"distro_queue_id": 456,
"release_id": 789,
"label_id": 321,
"release_cat": "ABC123",
"outlet_id": 12,
"outlet_name": "Spotify",
"previous_status": "transcoding",
"status": "batched"
}
}
FieldTipeDeskripsi
distro_queue_idintegerID antrean internal untuk pengiriman ini
release_idintegerRilis yang sedang didistribusikan
label_idintegerLabel pemilik rilis
release_catstring | nullReferensi katalog rilis Anda
outlet_idinteger | nullID outlet tujuan
outlet_namestring | nullNama outlet yang mudah dibaca
previous_statusstring | nullStatus sebelumnya; null jika baris tidak punya status sebelumnya yang dikenali
statusstringStatus baru

Setiap pengiriman webhook ditandatangani sehingga Anda dapat memverifikasi bahwa pengiriman tersebut memang berasal dari LabelGrid. Selalu verifikasi signature sebelum memproses peristiwa.

Setiap permintaan POST webhook menyertakan header berikut:

HeaderDeskripsi
X-Webhook-SignatureHMAC-SHA256 dari body permintaan mentah, heksadesimal huruf kecil, tanpa awalan algoritma
X-Webhook-TimestampTimestamp ISO 8601 pengiriman (nilainya sama dengan properti timestamp di body)
X-Webhook-EventPengidentifikasi peristiwa (mis. delivery.completed)
X-Webhook-IdID webhook yang menerima pengiriman
User-AgentLabelGrid-Webhooks/1.0
Content-Typeapplication/json
  • Algoritma: HMAC-SHA256
  • Pengodean: Heksadesimal huruf kecil
  • Awalan: Tidak ada; nilainya hanya berupa hex digest, bukan sha256=...
  • Konten yang ditandatangani: Seluruh body permintaan JSON mentah (body itu sendiri menyertakan timestamp sebagai properti, sehingga timestamp ikut ditandatangani secara implisit)
  1. Baca body permintaan mentah sebelum parsing atau transformasi JSON apa pun. Menserialisasi ulang JSON yang sudah diparsing dapat menghasilkan byte yang berbeda dan merusak signature.
  2. Hitung HMAC-SHA256(raw_body, your_webhook_secret) lalu ambil hex digest huruf kecilnya.
  3. Bandingkan dengan X-Webhook-Signature menggunakan perbandingan constant-time.
  4. (Disarankan) Tolak permintaan jika X-Webhook-Timestamp lebih lama dari jendela toleransi replay Anda; kami menyarankan 5 menit.
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
// Reject deliveries older than 5 minutes
if (abs(time() - strtotime($timestamp)) > 300) {
http_response_code(401);
exit('Stale delivery');
}
$payload = json_decode($rawBody, true);
// ... process the event
http_response_code(200);
const crypto = require('crypto');
// Express: capture raw body BEFORE any JSON middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body; // Buffer
const signature = req.header('X-Webhook-Signature') || '';
const timestamp = req.header('X-Webhook-Timestamp') || '';
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody)
.digest('hex');
const sigBuf = Buffer.from(signature, 'hex');
const expBuf = Buffer.from(expected, 'hex');
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
return res.status(401).send('Invalid signature');
}
if (Math.abs(Date.now() - new Date(timestamp).getTime()) > 5 * 60 * 1000) {
return res.status(401).send('Stale delivery');
}
const payload = JSON.parse(rawBody.toString('utf8'));
// ... process the event
res.sendStatus(200);
});
import hmac, hashlib
from datetime import datetime, timezone
raw_body = request.get_data() # Flask: bytes, before any JSON parsing
signature = request.headers.get('X-Webhook-Signature', '')
timestamp = request.headers.get('X-Webhook-Timestamp', '')
expected = hmac.new(
webhook_secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
return ('Invalid signature', 401)
delivery_time = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
if abs((datetime.now(timezone.utc) - delivery_time).total_seconds()) > 300:
return ('Stale delivery', 401)
return ('', 200) # process the event, then ack
  • Menserialisasi ulang body sebelum hashing. Framework yang otomatis memparsing JSON (Express express.json(), body permintaan default Laravel) kehilangan byte aslinya. Tangkap body mentah terlebih dahulu.
  • Menggunakan perbandingan yang bukan constant-time (==, ===). Rentan terhadap serangan timing; selalu gunakan hash_equals (PHP), crypto.timingSafeEqual (Node), hmac.compare_digest (Python), atau padanannya di bahasa Anda.
  • Mengharapkan awalan sha256=. Nilai header hanyalah hex digest tanpa awalan.
  • Melewatkan pemeriksaan timestamp. Tanpa itu, signature yang bocor dapat diputar ulang tanpa batas.

BatasanNilai
Batas waktu permintaan10 detik
Ukuran payload maksimum64 KB
Webhook maksimum per pengguna10

Jika endpoint Anda tidak merespons dalam 10 detik, pengiriman dianggap gagal dan dicoba ulang.

Jika endpoint Anda mengembalikan status non-2xx atau melewati batas waktu, LabelGrid mencoba ulang dengan backoff eksponensial:

PercobaanJeda sebelum percobaan ulang
1 → 230 detik
2 → 31 menit
3 → 42 menit
4 → 54 menit
5 → 68 menit
6 → 716 menit
7 → 832 menit
8 → 964 menit
9 → 10128 menit

Setiap interval mencakup jitter 0–30 detik. Setelah 10 percobaan (total waktu berlalu sekitar 4,5 jam), pengiriman dicatat sebagai gagal permanen dan tidak dicoba ulang lagi.

Jika endpoint sebuah webhook terus gagal — kegagalan pengiriman berturut-turut tanpa satu pun pengiriman berhasil di antaranya — LabelGrid menonaktifkan webhook tersebut secara otomatis agar berhenti mencoba ulang endpoint yang jelas tidak dapat menerima event. Penghitung kegagalan direset pada setiap pengiriman yang berhasil, sehingga gangguan sesekali tidak pernah menonaktifkan webhook; hanya kegagalan yang berkelanjutan dan tanpa henti yang melakukannya.

Ketika sebuah webhook dinonaktifkan dengan cara ini, pemiliknya menerima email. Email tersebut menyebutkan nama webhook dan URL endpoint-nya, serta jenis kegagalan yang memicu penonaktifan — misalnya waktu koneksi habis atau error HTTP yang berulang.

Mengaktifkan kembali bisa Anda lakukan sendiri: perbaiki endpoint Anda, lalu aktifkan kembali webhook di Profil → Webhooks. Mengaktifkan kembali webhook yang dinonaktifkan akan mereset penghitung kegagalannya. Daftar webhook menampilkan status aktif dan jumlah kegagalan terkini setiap webhook, sehingga Anda dapat mengenali endpoint bermasalah secara sekilas.

  • Kembalikan respons 2xx dengan cepat (dalam 10 detik)
  • Proses data secara asinkron setelah memberi konfirmasi
  • Verifikasi signature pada setiap permintaan (lihat Memverifikasi Signature Webhook)
  • Pantau hitungan kegagalan Anda dalam daftar webhook
  • Periksa log pengiriman saat menyelidiki peristiwa yang terlewat

  • Kirim pesan Slack saat rilis tayang
  • Kirim email ke tim Anda saat pengiriman gagal
  • Perbarui dasbor internal
  • Picu kampanye pemasaran saat rilis didistribusikan
  • Perbarui situs web Anda saat konten baru tersedia
  • Sinkronkan status ke alat manajemen proyek eksternal
  • Dapatkan peringatan seketika untuk kegagalan pengiriman
  • Pantau perkembangan distribusi secara waktu nyata
  • Pantau perubahan status peninjauan

  1. Periksa status - Apakah webhook berstatus Active?
  2. Verifikasi URL - Apakah endpoint dapat diakses dari internet?
  3. Periksa peristiwa - Apakah peristiwa yang tepat sudah dipilih?
  4. Tinjau log - Adakah kesalahan yang tercatat?
  1. Periksa endpoint Anda - Apakah mengembalikan 200 OK?
  2. Periksa waktu respons - Apakah merespons dalam batas waktu?
  3. Tinjau pesan kesalahan - Apa yang gagal?
  4. Uji secara manual - Kirim webhook uji

Jika secret webhook Anda bocor:

  1. Klik Regenerate Secret di pengaturan webhook
  2. Perbarui aplikasi Anda dengan secret baru
  3. Secret lama langsung berhenti berfungsi

Jika Anda memiliki pertanyaan tentang webhook, hubungi tim dukungan kami.

Belum menggunakan LabelGrid?

Semua yang baru saja Anda baca tersedia di platform kami.

Lihat kemampuan LabelGrid →