Pilih Auto, TokenLab Verified, atau Official untuk setiap permintaan, dengan harga yang ditampilkan di awal. Lihat yang baru

Webhook Tugas AI Asinkron: Verifikasi Tanda Tangan, Lalu Baca Tugasnya

CryptoCrypto
·28 September 2026·11 menit baca·Diperbarui 28 September 2026·32 tampilan
#webhook#tugas asinkron#integrasi API#keamanan
Webhook Tugas AI Asinkron: Verifikasi Tanda Tangan, Lalu Baca Tugasnya

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:

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-ID
  • X-Webhook-Timestamp, detik Unix
  • X-Webhook-Signature, diformat sebagai sha256=

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:

  1. 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.
  2. 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 id body sama dengan X-Webhook-ID.
  • Simpan ID acara bersama dengan item kerja Anda dalam satu penulisan atomik, didukung oleh batasan unik. Kemudian kembalikan 2xx dengan 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:

  1. Perbaiki penerima. Konfirmasi bahwa ia membaca byte mentah dan mengembalikan 2xx dengan cepat.
  2. Lanjutkan endpoint dengan PATCH {"is_active": true}. Ini mereset jumlah kegagalan.
  3. Kirim uji dengan POST …/test. 200 dari API uji hanya berarti upaya tersebut dicatat. Periksa riwayat pengiriman dan konfirmasi outcome == "delivered".
  4. Rekonsiliasi celah. Ambil ID tugas yang Anda simpan saat endpoint dijeda dan panggil GET /v1/tasks/{id} untuk masing-masing.
  5. 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.

  1. Panggil POST /v1/management/webhooks/{webhookId}/rotate-secret. Respons mengembalikan rahasia baru satu kali.
  2. Tambahkan rahasia baru ke daftar verifikasi Anda di penerima. Simpan juga yang lama di daftar itu.
  3. Terapkan perubahan penerima sebelum Anda menjatuhkan apa pun. Daftar harus menampung kedua rahasia sekaligus.
  4. Kirim uji dan konfirmasi outcome == "delivered" dalam riwayat.
  5. 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

Model yang baru dirilis

Bangun dengan model dalam panduan ini

Bandingkan harga, uji rute, dan ubah riset menjadi panggilan API yang berjalan.