DokümantasyonWebhook kullanımı

Webhook kullanımı

Transkripsiyon tamamlandığında veya başarısız olduğunda bildirim alın. Gelen isteğin imzasını doğrulayın.

Transkripsiyonun bitmesini sürekli sorgulamak yerine bildirim alabilirsiniz. Oluşturma isteğine webhook_url ekleyin. İşlem tamamlanırsa veya başarısız olursa bu adrese imzalı bir POST göndeririz.

Bu özellik yalnızca REST API'de bulunur. MCP sunucusu webhook parametresi kabul etmez.

Bildirim adresini belirtin

Video bağlantısı gönderirken veya parçalı yüklemeyi başlatırken webhook_url alanını POST /v1/transcriptions isteğinin JSON gövdesine ekleyin. Dosyayı tek istekte yüklüyorsanız aynı adı taşıyan form alanını kullanın:

{
  "platform": "youtube",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "webhook_url": "https://example.com/hooks/transcript1"
}

Adres HTTPS kullanmalı ve internetten erişilebilen bir IP adresine çözümlenmelidir. URL'ye kullanıcı adı veya parola eklemeyin. Özel ağ, loopback, link-local veya diğer herkese açık olmayan adresler için 422 invalid_request alırsınız. Adresi hem transkripsiyon oluşturulurken hem de her bildirim gönderiminde kontrol ederiz.

webhook_url gönderiyorsanız aynı istekte reuse_recent=true kullanmayın; bu iki seçenek birlikte çalışmaz.

Hangi olaylarda bildirim gelir?

typeGönderildiği durum
transcription.completedTranskripsiyon tamamlandı; durumu completed.
transcription.failedTranskripsiyon başarısız oldu; durumu failed. Hatanın code ve message alanları data.error içinde bulunur.

Transkripsiyonu iptal ederseniz webhook göndermeyiz.

Bildirimin içeriği

Bildirimde aşağıdaki gibi bir JSON gövdesi alırsınız:

{
  "type": "transcription.completed",
  "created_at": "2026-10-13T09:30:04.000Z",
  "data": {
    "id": "tr_8fK2Lm0PqRsTuVwXyZaBcD",
    "object": "transcription",
    "status": "completed",
    "transcript_source": "youtube_captions",
    "billed_minutes": 4,
    "error": null
  }
}

Olayın ne zaman oluştuğunu created_at gösterir. data içinde transkripsiyon nesnesi bulunur, ancak transkript metni (transcript) gönderilmez. Yukarıdaki nesne kısaltılmıştır. Metni almak için data içindeki id değerini kullanarak GET /v1/transcriptions/:id çağrısı yapın.

Her istekte Content-Type: application/json ve User-Agent değeri olarak transcript1-webhooks/1.0 göndeririz. Ayrıca aşağıda açıklanan imza başlığı bulunur.

Gelen isteğin imzasını doğrulayın

İmza her gönderimde X-Transcript1-Signature başlığındadır:

X-Transcript1-Signature: t=<unix seconds>,v1=<hex digest>
  • t: İsteğin imzalandığı anı, saniye cinsinden Unix zamanı olarak verir.
  • v1: "<t>.<raw body>" metninden hesaplanan HMAC-SHA256 imzasının hex biçimidir. Önce t değerini, sonra bir noktayı, ardından hiç değiştirmediğiniz istek gövdesini birleştirin. HMAC anahtarı olarak gizli imza anahtarınızın tamamını, whsec_ önekiyle birlikte kullanın.

İmzayı JSON'u ayrıştırmadan, gövdenin gelen baytları üzerinden doğrulayın. JSON'u ayrıştırıp yeniden oluşturursanız boşluklar veya alan sırası değişebilir ve imza eşleşmez. Karşılaştırma sabit zamanda çalışmalıdır. t ile şu an arasındaki fark çok büyükse isteği reddedin; aşağıdaki örnekler 5 dakikalık farka izin verir.

Node.js

import { Buffer } from 'node:buffer';
import crypto from 'node:crypto';
 
// header: X-Transcript1-Signature, rawBody: the request body exactly as received,
// secret: your full signing secret including the whsec_ prefix.
export const verifySignature = (secret, header, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) => {
  const fields = Object.fromEntries(header.split(',').map(part => part.trim().split('=')));
  const t = Number(fields.t);
 
  if (!Number.isInteger(t) || !fields.v1 || Math.abs(nowSeconds - t) > 300) {
    return false;
  }
 
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
 
  return fields.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(fields.v1), Buffer.from(expected));
};

Express kullanıyorsanız gövdeyi JSON'a çevirmeden ham metin olarak alın:

import process from 'node:process';
import express from 'express';
import { verifySignature } from './verify-signature.js';
 
const app = express();
 
app.post('/hooks/transcript1', express.text({ type: 'application/json' }), (request, response) => {
  const header = request.get('X-Transcript1-Signature') ?? '';
 
  if (!verifySignature(process.env.TRANSCRIPT1_WEBHOOK_SECRET, header, request.body)) {
    response.sendStatus(400);
    return;
  }
 
  const { type, data } = JSON.parse(request.body);
  console.log(type, data.id, data.status);
  response.sendStatus(200);
});

Python

import hashlib
import hmac
import time
 
 
def verify_signature(secret, header, raw_body, now=None):
    # header: the X-Transcript1-Signature header, raw_body: the request body exactly as received,
    # secret: your full signing secret including the whsec_ prefix.
    fields = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)
    try:
        t = int(fields["t"])
    except (KeyError, ValueError):
        return False
 
    now = int(time.time()) if now is None else now
    if abs(now - t) > 300:
        return False
 
    expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(fields.get("v1", ""), expected)

Flask kullanıyorsanız raw_body için request.get_data(as_text=True) değerini gönderin.

Webhook imza anahtarınız

Gizli imza anahtarınız whsec_ ile başlar. Her ekip için tek anahtar vardır; ekibin tüm transkripsiyonlarına ait webhook bildirimlerini bununla imzalarız. Anahtarı görmek veya yenilemek için paneldeki “Webhooks” sayfasını açın (/dashboard/webhooks). Yenilediğiniz anda eski anahtarın yerini yenisi alır. O andan sonra gönderdiğimiz bildirimleri yeni anahtarla imzalarız.

Gönderim ve yeniden deneme kuralları

  • Sunucunuz 2xx döndürürse bildirimi teslim edilmiş sayarız.
  • Başka bir durum kodu alırsak, bağlantı kurulamazsa veya 10 saniye içinde yanıt gelmezse gönderim başarısız sayılır.
  • Başarısız gönderimleri giderek daha uzun aralıklarla tekrarlarız. İlk gönderim dahil en fazla 6 deneme yaparız. Aralarındaki bekleme süreleri 2, 4, 8, 16 ve 32 dakikadır. Son deneme, ilkinden yaklaşık bir saat sonra yapılır.
  • Yönlendirmeleri takip etmeyiz. 3xx yanıtını da başarısız sayarız; bu yüzden doğrudan bildirimi işleyecek URL'yi belirtin.
  • Aynı bildirim birden fazla kez gelebilir. Örneğin sunucunuz zaman aşımından sonra yanıt verdiğinde yeniden gönderim yapabiliriz. data.id ve type çiftini kontrol ederek aynı olayı tekrar işlemeyin.

Son güncelleme 14 Ekim 2026