Webhook adalah petunjuk bertanda tangan bahwa suatu tugas telah mencapai status terminal. Webhook bukanlah catatan itu sendiri. Jadi aturannya singkat: verifikasi byte mentah, deduplikasi berdasarkan ID acara, jawab 2xx dengan cepat, lalu baca GET /v1/tasks/{id} untuk hasil dan status penagihan.
Webhook tugas ruang kerja (workspace) dirilis pada 2026-09-27. Anda mendapatkan Management API untuk siklus hidup webhook, pengiriman uji, rotasi rahasia, dan riwayat pengiriman. Dasbor dan manajemen MCP juga tersedia.
Satu koreksi di awal. Panduan pembuatan gambar asinkron kami sebelumnya menyatakan bahwa TokenLab tidak memiliki callback tugas; itu benar sebelum 2026-09-27, dan panduan tersebut telah diperbarui bersama dengan panduan ini.
Webhook atau polling? Gunakan keduanya
Keduanya memecahkan masalah yang berbeda, dan tidak ada yang menggantikan yang lain.
| Situasi | Gunakan |
|---|---|
| Anda ingin bereaksi saat tugas berakhir | Webhook |
| Anda memerlukan hasil atau biaya resmi | GET /v1/tasks/{id} |
| Penerima Anda sempat tidak aktif | Polling dengan ID tugas yang disimpan |
| Anda ingin cadangan saat pengiriman hilang | Polling pada interval lambat |
Webhook tidak menghilangkan kueri status dan tidak menambahkan batas polling. Simpan keduanya. Bahkan dengan webhook aktif, loop rekonsiliasi lambat yang membaca ID tugas tersimpan Anda adalah asuransi yang murah.
Jika Anda melakukan polling, gunakan poll_url, lakukan back off saat tugas tertunda, dan berhenti pada status terminal. Berhenti pada 401, 403, 404, atau saat error.retryable == false. Coba lagi 503 async_task_owner_unavailable dengan backoff. Tugas yang hilang atau kedaluwarsa akan mengembalikan 404 async_task_not_found. Lihat Panduan tugas dan polling asinkron untuk kontrak polling.
Tiga kredensial, tiga pekerjaan
Mencampuradukkan ini adalah cara tercepat untuk merusak penerima.
| Kredensial | Awalan | Fungsi | Catatan |
|---|---|---|---|
| Management Token | mt-… |
Membuat, mencantumkan, memperbarui, menghapus, menguji, dan merotasi webhook di /v1/management/webhooks* |
Dikirim sebagai Authorization: Bearer mt-…. Berlingkup ruang kerja |
| API key | sk-… |
Mengirimkan permintaan model dan membaca status tugas via GET /v1/tasks/{id} |
Ditolak oleh Management API |
| Signing secret | whsec_… |
Memverifikasi pengiriman di penerima Anda | Bukan Bearer token |
Dua hal tentang Management Token. Pertama, token ini juga mengotorisasi operasi manajemen ruang kerja lainnya, jadi ini bukan kredensial khusus webhook. Pilih ruang kerja yang sama dengan API key yang mengirimkan tugas Anda. Kedua, Anda membuatnya di Dasbor → API → Management Tokens. Lihat contoh Management API lainnya.
Simpan mt-… dan whsec_… hanya di backend Anda. Jangan pernah mengirimkan keduanya ke browser atau klien seluler.
Buat endpoint dan simpan rahasia segera
Panggilan buat mengembalikan 201 dengan id webhook dan secret satu kali pakai yang dimulai dengan whsec_…. Daftar, dapatkan, dan perbarui tidak akan pernah menampilkan rahasia itu lagi. Simpan segera setelah Anda melihatnya.
export TOKENLAB_MANAGEMENT_TOKEN="mt-your-management-token"
curl https://api.tokenlab.sh/v1/management/webhooks \
-H "Authorization: Bearer $TOKENLAB_MANAGEMENT_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"url":"https://your-app.example/webhooks/tokenlab","events":["task.completed","task.failed","task.timeout"],"description":"Production task results"}'
Endpoint yang sama dapat dikelola dengan tiga cara, dan ketiganya mengedit objek yang sama:
- Dasbor → API → Webhooks
- Management API
- MCP
Aturan URL sangat ketat. Endpoint harus berupa HTTPS publik. Tidak ada kredensial, string kueri, atau fragmen di URL. Pengalihan tidak diikuti, jadi 301 dihitung sebagai pengiriman gagal.
Anda dapat memiliki hingga 10 endpoint per ruang kerja. Pembuatan ke-11 akan mengembalikan 409 webhook_limit_reached.
| Metode | Path | Tujuan |
|---|---|---|
GET |
/v1/management/webhooks |
Mencantumkan endpoint |
POST |
/v1/management/webhooks |
Membuat endpoint |
GET |
/v1/management/webhooks/{webhookId} |
Membaca satu endpoint |
PATCH |
/v1/management/webhooks/{webhookId} |
Memperbarui, menjeda, atau melanjutkan |
DELETE |
/v1/management/webhooks/{webhookId} |
Menghapus |
POST |
/v1/management/webhooks/{webhookId}/rotate-secret |
Merotasi signing secret |
POST |
/v1/management/webhooks/{webhookId}/test |
Mengirim webhook.test |
GET |
/v1/management/webhooks/{webhookId}/deliveries?page=1&limit=50 |
Riwayat pengiriman, batas hingga 100 |
Jeda dengan PATCH {"is_active": false}. Lanjutkan dengan PATCH {"is_active": true}. Melanjutkan akan mereset jumlah kegagalan berturut-turut, yang penting setelah pemadaman.
Apa yang sebenarnya tiba
Setiap pengiriman adalah POST dengan amplop JSON. Bidang Management API adalah snake_case, tetapi bidang callback adalah camelCase. Jangan berasumsi satu casing terbawa ke yang lain.
| Bidang | Arti |
|---|---|
id |
ID Acara. Gunakan untuk deduplikasi |
type |
Tipe acara |
created |
Detik Unix |
data |
Payload acara, bentuk tergantung pada acara |
| Acara | Terpicu saat |
|---|---|
task.completed |
Tugas selesai dengan sukses |
task.failed |
Tugas berakhir dengan kegagalan |
task.timeout |
Tugas mencapai batas waktunya |
webhook.test |
Dikirim hanya oleh operasi uji |
task.completed membawa taskType (misalnya video atau image), taskId, model opsional, durationMs, resultUrls, dan settledCost.
task.failed membawa taskType, taskId, error, errorCode, retryable, dan refundOutcome.
task.timeout membawa taskType, taskId, refundOutcome, dan bidang waktu tunggu. Baca catatan tugas untuk nilai-nilai tersebut; set bidang tergantung pada tugasnya.
Langganan mencakup acara terminal di masa mendatang untuk tugas asinkron di ruang kerja. Hasil sinkron dan tugas historis tidak diputar ulang. Anda menerima setiap tugas ruang kerja untuk tipe acara yang Anda pilih, jadi cocokkan data.taskId dengan ID yang Anda simpan saat membuat tugas.
Bidang bisa saja tidak ada tergantung pada tugasnya. Itulah sebabnya GET /v1/tasks/{id} dengan kunci sk-… ruang kerja asli tetap menjadi sumber kebenaran untuk hasil dan status penagihan. Acara memberi tahu Anda sesuatu telah selesai. Catatan tugas memberi tahu Anda apa yang dihasilkannya dan berapa biayanya.
Satu hal lagi tentang retryable pada acara gagal. Ini menjelaskan kegagalan pembuatan, bukan instruksi untuk mengirim ulang secara otomatis. Pengiriman baru adalah tugas baru yang dapat ditagih.
Verifikasi byte mentah, lalu proses sekali
Setiap POST membawa tiga header:
X-Webhook-IDX-Webhook-Timestamp, detik UnixX-Webhook-Signature, diformat sebagaisha256=
Tanda tangan adalah HMAC-SHA256 atas string timestamp yang tepat, titik, dan byte body permintaan mentah, yang dikunci dengan rahasia whsec_… lengkap. Urutan itu penting, begitu pula body-nya.
Dua kesalahan yang paling sering merusak pemeriksaan tanda tangan:
- Memverifikasi JSON yang diurai. Jika Anda mengurai body dan membuat serialisasi ulang, byte akan berubah dan HMAC tidak akan cocok. Baca body mentah. Simpan sebagai byte sampai verifikasi lulus.
- Memverifikasi hanya dengan satu rahasia selama rotasi. Setelah Anda merotasi, pengiriman yang sudah dalam perjalanan mungkin masih membawa tanda tangan sebelumnya. Terima daftar rahasia untuk jendela waktu singkat.
Penerima Node di bawah ini bebas dependensi dan menggunakan node:http. Ini membaca body mentah, memverifikasi terhadap daftar rahasia, memeriksa jendela 300 detik, membandingkan id body dengan X-Webhook-ID, melakukan deduplikasi berdasarkan ID acara, mengantrekan, dan mengembalikan 204. Dedupe dalam sampel adalah set dalam memori; gunakan batasan database unik dalam produksi.
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';
// Selama rotasi, cantumkan rahasia whsec_ baru dan sebelumnya.
const SECRETS = (process.env.TOKENLAB_WEBHOOK_SECRETS ?? '').split(',').filter(Boolean);
const TOLERANCE_SECONDS = 300;
const seen = new Set(); // Gunakan batasan DB unik dalam produksi, bukan memori.
function verify(rawBody, headers) {
const timestamp = headers['x-webhook-timestamp'];
const signature = headers['x-webhook-signature'];
if (typeof timestamp !== 'string' || !/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
if (typeof signature !== 'string' || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
const received = Buffer.from(signature.slice(7), 'hex');
return SECRETS.some((secret) => {
const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
return timingSafeEqual(expected, received);
});
}
const server = createServer((req, res) => {
if (req.method !== 'POST' || req.url !== '/webhooks/tokenlab') {
res.writeHead(404).end();
return;
}
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
const rawBody = Buffer.concat(chunks); // verifikasi byte yang tepat, sebelum JSON.parse
if (!verify(rawBody, req.headers)) {
res.writeHead(401).end();
return;
}
const event = JSON.parse(rawBody.toString('utf8'));
if (event.id !== req.headers['x-webhook-id']) {
res.writeHead(400).end();
return;
}
if (!seen.has(event.id)) {
seen.add(event.id);
enqueue(event); // serahkan; lakukan pekerjaan lambat di luar permintaan
}
res.writeHead(204).end();
});
});
function enqueue(event) {
console.log('queued', event.type, event.data?.taskId);
}
server.listen(Number(process.env.PORT ?? 3000));
Penerima diuji secara lokal pada 2026-09-28 terhadap permintaan yang ditandatangani persis seperti pengirim produksi: pengiriman valid, pengiriman duplikat, rahasia sebelumnya selama rotasi, rahasia salah, timestamp basi, ketidakcocokan ID header dan body, body yang dirusak, dan JSON yang diserialisasi ulang. Delapan kasus, semuanya lulus. Satu duplikat diantrekan sekali.
Sisi Python adalah fungsi verifikasi tunggal. Ini membandingkan tanda tangan dengan hmac.compare_digest dan mengharapkan byte body mentah dari request.get_data() Flask atau await request.body() FastAPI.
import hashlib
import hmac
import re
import time
TOLERANCE_SECONDS = 300
SIGNATURE_RE = re.compile(r"^sha256=[a-f0-9]{64}$")
def verify_webhook(raw_body: bytes, headers, secrets: list[str]) -> bool:
"""Periksa webhook TokenLab terhadap satu atau lebih rahasia whsec_.
raw_body harus berupa byte permintaan yang tepat (Flask: request.get_data(),
FastAPI/Starlette: await request.body()), dibaca sebelum penguraian JSON apa pun.
"""
timestamp = headers.get("x-webhook-timestamp", "")
signature = headers.get("x-webhook-signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
return False
if not SIGNATURE_RE.match(signature):
return False
received = signature.removeprefix("sha256=")
for secret in secrets:
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
if hmac.compare_digest(expected, received):
return True
return False
Diuji pada 2026-09-28: valid, rahasia sebelumnya, rahasia salah, timestamp basi, body dirusak, dan body yang diserialisasi ulang dengan spasi json.dumps default. Enam kasus, semuanya lulus.
Di luar tanda tangan, lakukan tiga hal pada setiap permintaan:
- Tolak timestamp lebih dari 300 detik dari sekarang. Itu adalah 5 menit, dan itu membatasi seberapa lama replay bisa terjadi.
- Konfirmasi
idbody sama denganX-Webhook-ID. - Simpan ID acara bersama dengan item kerja Anda dalam satu penulisan atomik, didukung oleh batasan unik. Kemudian kembalikan
2xxdengan cepat dan lakukan pekerjaan berat dari antrean Anda sendiri.
Pengiriman dapat berulang dan urutan tidak dijamin. Jendela timestamp membatasi usia replay. Deduplikasi ID acara mencegah pemrosesan ganda.
Percobaan ulang, jeda otomatis, dan buku panduan pemulihan
Setiap siklus pengiriman melakukan hingga tiga upaya.
| Upaya | Tunggu sebelumnya | Batas waktu upaya |
|---|---|---|
| 1 | tidak ada | 10 s |
| 2 | 1 s | 10 s |
| 3 | 4 s | 10 s |
Sumber: Panduan webhook TokenLab, diamati 2026-09-28.
Setiap upaya mendapatkan timestamp dan tanda tangan baru. Itu berarti pemeriksaan tanda tangan Anda harus menggunakan timestamp dari permintaan yang sama, bukan nilai yang di-cache.
Respons yang dapat dicoba ulang: kegagalan jaringan, 429, dan 5xx. Tidak dicoba ulang dalam siklus: 4xx lainnya, pengalihan, dan target jaringan tidak valid. Kegagalan sementara dapat memicu percobaan ulang nanti dari acara yang sama dengan ID pengiriman yang sama, yang merupakan alasan lain mengapa dedupe tidak opsional.
Sepuluh siklus gagal berturut-turut menjeda endpoint secara otomatis.
Saat penerima Anda tidak aktif, kerjakan ini secara berurutan:
- Perbaiki penerima. Konfirmasi bahwa ia membaca byte mentah dan mengembalikan
2xxdengan cepat. - Lanjutkan endpoint dengan
PATCH {"is_active": true}. Ini mereset jumlah kegagalan. - Kirim uji dengan
POST …/test.200dari API uji hanya berarti upaya tersebut dicatat. Periksa riwayat pengiriman dan konfirmasioutcome == "delivered". - Rekonsiliasi celah. Ambil ID tugas yang Anda simpan saat endpoint dijeda dan panggil
GET /v1/tasks/{id}untuk masing-masing. - Baru setelah itu percayai aliran webhook lagi.
Riwayat pengiriman memberi Anda outcome, http_status, attempts, dan delivered_at. Ini hanya menyimpan metadata, bukan payload. Acara lama tidak dapat diputar ulang secara manual, jadi langkah 4 tidak opsional. ID tugas tersimpan Anda adalah jalur pemulihan.
Merotasi rahasia tanpa menjatuhkan acara
Rotasi tidak dapat dibalik, jadi rencanakan jendela sebelum Anda mulai.
- Panggil
POST /v1/management/webhooks/{webhookId}/rotate-secret. Respons mengembalikan rahasia baru satu kali. - Tambahkan rahasia baru ke daftar verifikasi Anda di penerima. Simpan juga yang lama di daftar itu.
- Terapkan perubahan penerima sebelum Anda menjatuhkan apa pun. Daftar harus menampung kedua rahasia sekaligus.
- Kirim uji dan konfirmasi
outcome == "delivered"dalam riwayat. - Setelah jendela singkat, hapus rahasia lama dan terapkan kembali.
Pengiriman dalam perjalanan mungkin masih membawa tanda tangan sebelumnya. Jika Anda menukar rahasia dalam satu langkah, Anda menjatuhkan acara tersebut. Verifikator yang hanya menampung satu rahasia dapat menolak pengiriman yang ditandatangani tepat sebelum rotasi.
Mengelola webhook dari MCP
Jika Anda menjalankan TokenLab dari agen, server MCP mengekspos siklus hidup yang sama. Gunakan @tokenlabai/mcp-server dengan profil full. Alat-alatnya adalah list_webhooks, create_webhook, get_webhook, update_webhook, delete_webhook, rotate_webhook_secret, test_webhook, dan list_webhook_deliveries.
Server membaca Management Token dari TOKENLAB_MANAGEMENT_TOKEN. Paket terbaru yang diterbitkan diamati pada 2026-09-28 adalah 0.6.24. MCP mengedit endpoint yang sama yang Anda lihat di Dasbor, jadi tidak ada status terpisah untuk direkonsiliasi.
FAQ
Apakah tugas gambar mengirim webhook?
Ya. Setiap tugas asinkron di ruang kerja, termasuk tugas gambar, mengirim acara terminalnya ke endpoint yang berlangganan tipe acara tersebut. Bidang taskType pada payload memberi tahu Anda jenis tugas apa itu, misalnya video atau image. Hasil sinkron tidak tercakup.
Apa yang terjadi jika endpoint saya tidak aktif?
Setiap siklus mencoba ulang hingga tiga kali. Sepuluh siklus gagal berturut-turut menjeda endpoint secara otomatis. Kegagalan pengiriman sementara dapat dicoba ulang nanti dengan ID pengiriman yang sama. Setelah endpoint dijeda, acara dari jeda tersebut tidak dikirim nanti dan tidak dapat diputar ulang secara manual. Perbaiki penerima, lanjutkan endpoint, kirim uji, lalu rekonsiliasi tugas yang Anda buat selama celah dengan memanggil GET /v1/tasks/{id} dengan ID tugas tersimpan Anda.
Bisakah saya memutar ulang acara lama?
Tidak. Riwayat pengiriman hanya menampung metadata, bukan payload, dan tidak ada pemutaran ulang manual. Jendela timestamp juga menolak apa pun yang lebih lama dari 300 detik. Rekonsiliasi melalui API tugas adalah cara yang didukung untuk mengejar ketinggalan.
Apakah task.failed dengan retryable: true aman untuk dikirim ulang secara otomatis?
Tidak. retryable menjelaskan kegagalan pembuatan. Ini bukan instruksi untuk mengirim ulang. Pengiriman baru adalah tugas baru yang dapat ditagih, jadi putuskan percobaan ulang sendiri dan perhitungkan biayanya.
Apakah API kompatibilitas Seedance menggunakan webhook ini?
Tidak. callback_url per-permintaan-nya adalah kontrak terpisah dengan payload-nya sendiri. Ini tidak menggunakan acara ruang kerja atau header HMAC ini, jadi jangan arahkan satu verifikator ke keduanya.
Mulai dengan kontrak lengkap di panduan webhook, lalu buat API key dan aktifkan endpoint pertama Anda di ruang kerja yang mengirimkan tugas Anda.
Sumber
- https://docs.tokenlab.sh/guides/webhooksDiamati pada 2026-09-28
- https://docs.tokenlab.sh/guides/async-jobs-pollingDiamati pada 2026-09-28
- https://www.npmjs.com/package/@tokenlabai/mcp-serverDiamati pada 2026-09-28



