DocumentatieWebhooks instellen

Webhooks instellen

Ontvang een ondertekend request zodra een transcriptie klaar is of mislukt. Controleer de handtekening voordat je het verwerkt.

Wil je niet blijven pollen? Geef bij het aanmaken van de transcriptie een webhook_url mee. Zodra de transcriptie klaar is of mislukt, sturen we een ondertekend POST-request naar die URL.

Je kunt webhooks alleen via de REST API instellen. De MCP-server heeft hiervoor geen parameter.

Geef je webhook-URL mee

Stuur webhook_url mee met POST /v1/transcriptions. Bij een ondersteunde videolink of het aanmaken van een upload in chunks zet je die in de JSON-body. Upload je een bestand in één request, gebruik dan een formulierveld:

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

Gebruik een HTTPS-URL zonder gebruikersnaam of wachtwoord. De URL moet uitkomen op een openbaar adres. Verwijst die naar een privéadres, loopback-adres, link-local-adres of een ander niet-openbaar adres, dan krijg je 422 invalid_request. We controleren het adres bij het aanmaken en opnieuw bij elke poging om de webhook te versturen.

Je kunt webhook_url niet samen met reuse_recent=true gebruiken.

Welke events ontvang je?

typeWanneer ontvang je dit?
transcription.completedZodra de status completed is.
transcription.failedZodra de status failed is. In data.error staan de code en message.

Annuleer je een transcriptie, dan sturen we geen webhook.

Wat staat er in het request?

De webhook heeft een JSON-body:

{
  "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
  }
}

In created_at staat wanneer het event is aangemaakt. data bevat het transcriptieobject, maar niet het veld transcript. Het voorbeeld hierboven is ingekort. Gebruik de id uit data om de tekst op te halen met GET /v1/transcriptions/:id.

Je krijgt ook Content-Type: application/json en een User-Agent met de waarde transcript1-webhooks/1.0. De header met de handtekening lees je hieronder.

Controleer de handtekening

Elk webhookrequest bevat X-Transcript1-Signature:

X-Transcript1-Signature: t=<unix seconds>,v1=<hex digest>
  • t geeft in Unix-seconden aan wanneer we het request hebben ondertekend.
  • v1 bevat de HMAC-SHA256 als hexwaarde. Bereken die over "<t>.<raw body>": t, een punt en de body zoals je die hebt ontvangen. Gebruik je volledige geheime sleutel, dus inclusief whsec_.

Controleer de handtekening op de oorspronkelijke bytes, vóór je de JSON parset. Als je JSON eerst parset en daarna weer omzet naar tekst, kunnen spaties of de volgorde van velden veranderen. De handtekening klopt dan niet meer. Vergelijk handtekeningen in constante tijd. Weiger ook requests waarvan t te ver van de huidige tijd ligt; de voorbeelden hieronder staan een verschil van 5 minuten toe.

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));
};

Laat Express de body als tekst inlezen. Zo blijft die intact voor de controle:

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)

Gebruik je Flask? Geef dan request.get_data(as_text=True) door als raw_body.

Je geheime sleutel beheren

Je geheime sleutel begint met whsec_ en geldt voor je hele team. We ondertekenen hiermee de webhooks van alle transcripties die je team aanmaakt. Op “Webhooks”, onder /dashboard/webhooks, kun je de sleutel bekijken of vervangen. Een nieuwe sleutel gaat direct in: elke webhook die we daarna versturen gebruikt die nieuwe sleutel.

Aflevering en nieuwe pogingen

  • Antwoordt je server met 2xx, dan beschouwen we de webhook als afgeleverd.
  • Een andere statuscode, een verbindingsfout of geen antwoord binnen 10 seconden telt als een mislukte poging.
  • Na een mislukte poging proberen we het opnieuw, tot maximaal 6 pogingen in totaal. De wachttijd loopt exponentieel op: 2, 4, 8, 16 en 32 minuten. De laatste poging is daardoor ongeveer een uur na de eerste.
  • Geef meteen de uiteindelijke URL op. transcript1 volgt geen redirects; ook 3xx telt als mislukt.
  • Dezelfde webhook kan vaker aankomen, bijvoorbeeld als je server pas na de time-out antwoordt. Voorkom dubbele verwerking met data.id en type.

Laatst bijgewerkt 14 oktober 2026