Webhook, bir görevin terminal durumuna ulaştığına dair imzalı bir ipucudur. Kaydın kendisi değildir. Bu yüzden kural kısadır: ham baytları doğrulayın, etkinlik kimliği (event ID) ile tekilleştirin, 2xx yanıtını hızlıca verin, ardından sonuç ve faturalandırma durumu için GET /v1/tasks/{id} isteğini okuyun.
Çalışma alanı (workspace) görev webhook'ları 27-09-2026 tarihinde kullanıma sunuldu. Webhook yaşam döngüsü, test teslimatı, gizli anahtar (secret) rotasyonu ve teslimat geçmişi için bir Management API elde edersiniz. Dashboard ve MCP yönetimi de mevcuttur.
Öncelikle bir düzeltme. Daha önceki async görsel oluşturma kılavuzumuzda TokenLab'in görev geri bildirimi (callback) olmadığı belirtilmişti; bu 27-09-2026 öncesinde doğruydu ve o kılavuz bununla birlikte güncellendi.
Webhook'lar mı yoksa polling mi? Her ikisini de kullanın
Farklı sorunları çözerler ve hiçbiri diğerinin yerini tutmaz.
| Durum | Şuna başvurun |
|---|---|
| Bir görev bittiği anda tepki vermek istiyorsanız | Webhook |
| Yetkili sonuca veya maliyete ihtiyacınız varsa | GET /v1/tasks/{id} |
| Alıcınız bir süredir kapalıysa | Depolanan görev kimlikleri ile Polling |
| Teslimatlar kaybolduğunda bir yedek istiyorsanız | Yavaş aralıklarla Polling |
Webhook'lar durum sorgularını ortadan kaldırmaz ve bir polling sınırı eklemez. Her ikisini de tutun. Webhook'lar açık olsa bile, depolanan görev kimliklerinizi okuyan yavaş bir mutabakat döngüsü ucuz bir sigortadır.
Eğer polling yapıyorsanız, poll_url kullanın, görev beklemedeyken geri çekilin (back off) ve terminal durumlarında durun. 401, 403, 404 durumlarında veya error.retryable == false olduğunda durun. 503 async_task_owner_unavailable hatalarını geri çekilme ile yeniden deneyin. Eksik veya süresi dolmuş bir görev 404 async_task_not_found döndürür. Polling sözleşmesi için Async jobs and polling guide sayfasına bakın.
Üç kimlik bilgisi, üç iş
Bunları karıştırmak, bozuk bir alıcıya giden en hızlı yoldur.
| Kimlik Bilgisi | Önek | Ne işe yarar | Notlar |
|---|---|---|---|
| Management Token | mt-… |
/v1/management/webhooks* üzerinde webhook oluşturur, listeler, günceller, siler, test eder ve rotasyonunu yapar |
Authorization: Bearer mt-… olarak gönderilir. Çalışma alanı kapsamlıdır |
| API key | sk-… |
Model istekleri gönderir ve GET /v1/tasks/{id} aracılığıyla görev durumunu okur |
Management API tarafından reddedilir |
| Signing secret | whsec_… |
Alıcınızdaki teslimatları doğrular | Asla Bearer token değildir |
Management Token hakkında iki şey. Birincisi, diğer çalışma alanı yönetimi işlemlerini de yetkilendirir, yani sadece webhook'a özel bir kimlik bilgisi değildir. Görevlerinizi gönderen API key ile aynı çalışma alanını seçin. İkincisi, bunu Dashboard → API → Management Tokens kısmından oluşturursunuz. Başka bir Management API örneğine bakın.
mt-… ve whsec_… anahtarlarını sadece arka ucunuzda tutun. Bunları asla bir tarayıcıya veya mobil istemciye göndermeyin.
Bir uç nokta oluşturun ve gizli anahtarı hemen saklayın
Oluşturma çağrısı, webhook id'si ve whsec_… ile başlayan tek seferlik bir secret ile 201 döndürür. Listeleme, alma ve güncelleme işlemleri bu gizli anahtarı bir daha asla göstermez. Gördüğünüz anda saklayın.
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"}'
Aynı uç noktalar üç yolla yönetilebilir ve üçü de aynı nesneleri düzenler:
- Dashboard → API → Webhooks
- Management API
- MCP
URL kuralları katıdır. Uç nokta herkese açık HTTPS olmalıdır. URL içinde kimlik bilgisi, sorgu dizisi veya parça (fragment) bulunmamalıdır. Yönlendirmeler takip edilmez, bu nedenle bir 301 başarısız teslimat olarak sayılır.
Çalışma alanı başına en fazla 10 uç noktanız olabilir. 11. oluşturma işlemi 409 webhook_limit_reached döndürür.
| Metot | Yol | Amaç |
|---|---|---|
GET |
/v1/management/webhooks |
Uç noktaları listele |
POST |
/v1/management/webhooks |
Uç nokta oluştur |
GET |
/v1/management/webhooks/{webhookId} |
Bir uç noktayı oku |
PATCH |
/v1/management/webhooks/{webhookId} |
Güncelle, duraklat veya devam ettir |
DELETE |
/v1/management/webhooks/{webhookId} |
Sil |
POST |
/v1/management/webhooks/{webhookId}/rotate-secret |
İmzalama gizli anahtarını döndür |
POST |
/v1/management/webhooks/{webhookId}/test |
webhook.test gönder |
GET |
/v1/management/webhooks/{webhookId}/deliveries?page=1&limit=50 |
Teslimat geçmişi, 100'e kadar limit |
PATCH {"is_active": false} ile duraklatın. PATCH {"is_active": true} ile devam ettirin. Devam ettirmek, bir kesintiden sonra önemli olan ardışık hata sayısını sıfırlar.
Gerçekte ne gelir
Her teslimat, JSON zarfı içeren bir POST isteğidir. Management API alanları snake_case'dir, ancak geri bildirim alanları camelCase'dir. Bir yazım biçiminin diğerine taşınacağını varsaymayın.
| Alan | Anlamı |
|---|---|
id |
Etkinlik kimliği. Tekilleştirmek için kullanın |
type |
Etkinlik türü |
created |
Unix saniyeleri |
data |
Etkinlik yükü, şekli etkinliğe bağlıdır |
| Etkinlik | Ne zaman tetiklenir |
|---|---|
task.completed |
Görev başarıyla bitti |
task.failed |
Görev başarısızlıkla sonuçlandı |
task.timeout |
Görev zaman sınırına ulaştı |
webhook.test |
Sadece test işlemi tarafından gönderilir |
task.completed; taskType (örneğin video veya image), taskId, isteğe bağlı model, durationMs, resultUrls ve settledCost bilgilerini taşır.
task.failed; taskType, taskId, error, errorCode, retryable ve refundOutcome bilgilerini taşır.
task.timeout; taskType, taskId, refundOutcome ve bekleme süresi alanlarını taşır. Bu değerler için görev kaydını okuyun; alan kümesi göreve bağlıdır.
Abonelikler, çalışma alanındaki async görevler için gelecekteki terminal etkinliklerini kapsar. Senkron sonuçlar ve geçmiş görevler tekrar oynatılmaz. Seçtiğiniz etkinlik türleri için her çalışma alanı görevini alırsınız, bu nedenle data.taskId değerini görevi oluşturduğunuzda sakladığınız kimlik ile eşleştirin.
Alanlar göreve bağlı olarak eksik olabilir. Bu yüzden orijinal çalışma alanının sk-… anahtarı ile yapılan GET /v1/tasks/{id} isteği, sonuç ve faturalandırma durumu için doğruluk kaynağı olarak kalır. Etkinlik size bir şeyin bittiğini söyler. Görev kaydı ise size ne ürettiğini ve ne kadara mal olduğunu söyler.
Başarısız bir etkinlikteki retryable hakkında bir şey daha. Bu, oluşturma hatasını tanımlar, otomatik olarak yeniden gönderme talimatı değildir. Yeni bir gönderim, yeni ve faturalandırılabilir bir görevdir.
Ham baytları doğrulayın, ardından bir kez işleyin
Her POST üç başlık taşır:
X-Webhook-IDX-Webhook-Timestamp, Unix saniyeleriX-Webhook-Signature,sha256=olarak biçimlendirilmiş
İmza, tam whsec_… gizli anahtarı ile anahtarlanmış tam zaman damgası dizisi, bir nokta ve ham istek gövdesi baytları üzerinden HMAC-SHA256'dır. Sıralama önemlidir, gövde de öyle.
İki hata imza kontrollerini her şeyden daha fazla bozar:
- Ayrıştırılmış JSON'u doğrulamak. Gövdeyi ayrıştırıp yeniden serileştirirseniz, baytlar değişir ve HMAC eşleşmez. Ham gövdeyi okuyun. Doğrulama geçene kadar bayt olarak tutun.
- Rotasyon sırasında sadece bir gizli anahtar ile doğrulamak. Rotasyon yaptıktan sonra, halihazırda iletimde olan teslimatlar önceki imzayı taşıyor olabilir. Kısa bir süre için gizli anahtar listesini kabul edin.
Aşağıdaki Node alıcısı bağımlılıksızdır ve node:http kullanır. Ham gövdeyi okur, gizli anahtar listesine göre doğrular, 300 saniyelik pencereyi kontrol eder, gövde id'sini X-Webhook-ID ile karşılaştırır, etkinlik kimliği ile tekilleştirir, kuyruğa alır ve 204 döndürür. Örnekteki tekilleştirme bellek içi bir kümedir; üretimde benzersiz bir veritabanı kısıtlaması kullanın.
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';
// Rotasyon sırasında hem yeni hem de önceki whsec_ gizli anahtarını listeleyin.
const SECRETS = (process.env.TOKENLAB_WEBHOOK_SECRETS ?? '').split(',').filter(Boolean);
const TOLERANCE_SECONDS = 300;
const seen = new Set(); // Üretimde bellek değil, benzersiz bir DB kısıtlaması kullanın.
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); // JSON.parse öncesinde tam baytları doğrulayın
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); // teslim edin; yavaş işleri isteğin dışında yapın
}
res.writeHead(204).end();
});
});
function enqueue(event) {
console.log('queued', event.type, event.data?.taskId);
}
server.listen(Number(process.env.PORT ?? 3000));
Alıcı, 28-09-2026 tarihinde üretim göndericisiyle aynı şekilde imzalanmış isteklere karşı yerel olarak test edildi: geçerli teslimat, yinelenen teslimat, rotasyon sırasında önceki gizli anahtar, yanlış gizli anahtar, eski zaman damgası, başlık ve gövde kimliği uyuşmazlığı, kurcalanmış gövde ve yeniden serileştirilmiş JSON. Sekiz durumun tamamı geçti. Bir kopya bir kez kuyruğa alındı.
Python tarafı tek bir doğrulama fonksiyonudur. İmzaları hmac.compare_digest ile karşılaştırır ve Flask'ın request.get_data() veya FastAPI'nin await request.body() fonksiyonlarından gelen ham gövde baytlarını bekler.
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:
"""Bir TokenLab webhook'unu bir veya daha fazla whsec_ gizli anahtarına karşı kontrol edin.
raw_body, herhangi bir JSON ayrıştırmasından önce okunan tam istek baytları olmalıdır
(Flask: request.get_data(), FastAPI/Starlette: await request.body()).
"""
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
28-09-2026 tarihinde test edildi: geçerli, önceki gizli anahtar, yanlış gizli anahtar, eski zaman damgası, kurcalanmış gövde ve varsayılan json.dumps boşlukları ile yeniden serileştirilmiş bir gövde. Altı durumun tamamı geçti.
İmzanın ötesinde, her istekte üç şey yapın:
- Şu andan itibaren 300 saniyeden daha eski zaman damgalarını reddedin. Bu 5 dakikadır ve bir tekrarın (replay) ne kadar eski olabileceğini sınırlar.
- Gövde
id'sininX-Webhook-ID'ye eşit olduğunu onaylayın. - Etkinlik kimliğini, benzersiz bir kısıtlama ile desteklenen tek bir atomik yazma işleminde iş öğenizle birlikte saklayın. Ardından
2xxyanıtını hızlıca döndürün ve ağır işleri kendi kuyruğunuzdan yapın.
Teslimatlar tekrarlanabilir ve sıralama garanti edilmez. Zaman damgası penceresi tekrar yaşını sınırlar. Etkinlik kimliği ile tekilleştirme, çift işlemeyi önler.
Yeniden denemeler, otomatik duraklatma ve kurtarma rehberi
Her teslimat döngüsü üç denemeye kadar çıkar.
| Deneme | Öncesindeki bekleme | Deneme zaman aşımı |
|---|---|---|
| 1 | yok | 10 s |
| 2 | 1 s | 10 s |
| 3 | 4 s | 10 s |
Kaynak: TokenLab webhook kılavuzu, 28-09-2026 tarihinde gözlemlendi.
Her deneme yeni bir zaman damgası ve imza alır. Bu, imza kontrolünüzün önbelleğe alınmış bir değer yerine aynı isteğin zaman damgasını kullanması gerektiği anlamına gelir.
Yeniden denenebilir yanıtlar: ağ hataları, 429 ve 5xx. Döngü içinde yeniden denenmeyenler: diğer 4xx hataları, yönlendirmeler ve geçersiz ağ hedefleri. Geçici hatalar, aynı etkinlik için aynı teslimat kimliğiyle daha sonra yeniden denemeleri tetikleyebilir, bu da tekilleştirmenin isteğe bağlı olmadığının bir başka nedenidir.
Ardışık on başarısız döngü, uç noktayı otomatik olarak duraklatır.
Alıcınız kapalı olduğunda, bunu sırasıyla uygulayın:
- Alıcıyı düzeltin. Ham baytları okuduğunu ve hızlıca
2xxdöndürdüğünü onaylayın. - Uç noktayı
PATCH {"is_active": true}ile devam ettirin. Bu, hata sayısını sıfırlar. POST …/testile bir test gönderin. Test API'sinden gelen bir200, sadece denemenin kaydedildiği anlamına gelir. Teslimat geçmişini kontrol edin veoutcome == "delivered"olduğunu onaylayın.- Boşluğu kapatın. Uç nokta duraklatıldığında sakladığınız görev kimliklerini alın ve her biri için
GET /v1/tasks/{id}çağrısı yapın. - Ancak o zaman webhook akışına tekrar güvenin.
Teslimat geçmişi size outcome, http_status, attempts ve delivered_at bilgilerini verir. Sadece meta verileri saklar, yükleri saklamaz. Eski etkinlikler manuel olarak tekrar oynatılamaz, bu yüzden 4. adım isteğe bağlı değildir. Sakladığınız görev kimlikleri kurtarma yoludur.
Etkinlikleri düşürmeden gizli anahtarı döndürün
Rotasyon geri alınamaz, bu yüzden başlamadan önce pencereyi planlayın.
POST /v1/management/webhooks/{webhookId}/rotate-secretçağrısı yapın. Yanıt, yeni gizli anahtarı bir kez döndürür.- Yeni gizli anahtarı alıcıdaki doğrulama listenize ekleyin. Eskisini de o listede tutun.
- Herhangi bir şeyi düşürmeden önce alıcı değişikliğini dağıtın. Liste her iki gizli anahtarı da aynı anda tutmalıdır.
- Bir test gönderin ve geçmişte
outcome == "delivered"olduğunu onaylayın. - Kısa bir süre sonra eski gizli anahtarı kaldırın ve yeniden dağıtın.
İletimdeki teslimatlar önceki imzayı taşıyor olabilir. Gizli anahtarları tek adımda değiştirirseniz, bu etkinlikleri kaybedersiniz. Sadece bir gizli anahtar tutan bir doğrulayıcı, rotasyondan hemen önce imzalanan teslimatları reddedebilir.
Webhook'ları MCP'den yönetin
TokenLab'i bir ajandan yönetiyorsanız, MCP sunucusu aynı yaşam döngüsünü sunar. full profili ile @tokenlabai/mcp-server kullanın. Araçlar; list_webhooks, create_webhook, get_webhook, update_webhook, delete_webhook, rotate_webhook_secret, test_webhook ve list_webhook_deliveries'dir.
Sunucu, Management Token'ı TOKENLAB_MANAGEMENT_TOKEN değişkeninden okur. 28-09-2026 tarihinde gözlemlenen en son yayınlanan paket 0.6.24'tür. MCP, Dashboard'da gördüğünüz aynı uç noktaları düzenler, bu nedenle mutabık kalınacak ayrı bir durum yoktur.
SSS
Görsel görevleri webhook gönderir mi?
Evet. Çalışma alanındaki her async görev, görsel görevleri dahil, terminal etkinliğini o etkinlik türüne abone olan uç noktalara gönderir. Yükteki taskType alanı size görevin ne tür olduğunu söyler, örneğin video veya image. Senkron sonuçlar kapsanmaz.
Uç noktam kapalıysa ne olur?
Her döngü üç defaya kadar yeniden dener. Ardışık on başarısız döngü, uç noktayı otomatik olarak duraklatır. Geçici teslimat hataları daha sonra aynı teslimat kimliğiyle yeniden denenebilir. Uç nokta duraklatıldığında, duraklama sırasındaki etkinlikler daha sonra teslim edilmez ve manuel olarak tekrar oynatılamaz. Alıcıyı düzeltin, uç noktayı devam ettirin, bir test gönderin, ardından boşluk sırasında oluşturduğunuz görevleri sakladığınız görev kimlikleriyle GET /v1/tasks/{id} çağrısı yaparak mutabık kalın.
Eski bir etkinliği tekrar oynatabilir miyim?
Hayır. Teslimat geçmişi yükleri değil, sadece meta verileri tutar ve manuel tekrar oynatma yoktur. Zaman damgası penceresi de 300 saniyeden eski her şeyi reddeder. Görev API'si aracılığıyla mutabakat, yakalamak için desteklenen yoldur.
retryable: true olan task.failed otomatik olarak yeniden gönderilmek için güvenli midir?
Hayır. retryable, oluşturma hatasını tanımlar. Yeniden gönderme talimatı değildir. Yeni bir gönderim, yeni ve faturalandırılabilir bir görevdir, bu yüzden yeniden deneme kararını kendiniz verin ve maliyeti hesaba katın.
Seedance uyumluluk API'si bu webhook'ları kullanıyor mu?
Hayır. İstek başına callback_url'si, kendi yükü olan ayrı bir sözleşmedir. Çalışma alanı etkinliklerini veya bu HMAC başlıklarını kullanmaz, bu yüzden bir doğrulayıcıyı her ikisine de yönlendirmeyin.
Tam sözleşme ile webhook kılavuzundan başlayın, ardından bir API key oluşturun ve görevlerinizi gönderen çalışma alanında ilk uç noktanızı açın.
Kaynaklar
- https://docs.tokenlab.sh/guides/webhooks2026-09-28 tarihinde gözlendi
- https://docs.tokenlab.sh/guides/async-jobs-polling2026-09-28 tarihinde gözlendi
- https://www.npmjs.com/package/@tokenlabai/mcp-server2026-09-28 tarihinde gözlendi



