DocumentationWebhooks
Webhooks
Recevez un webhook signé à la fin d'une transcription ou en cas d'échec, et vérifiez sa signature.
Pour être averti quand une transcription est prête ou a échoué, renseignez webhook_url lors de sa création. transcript1 enverra un POST signé à cette adresse.
Les webhooks sont réservés à l'API REST. Vous ne pouvez pas les configurer depuis le serveur MCP, qui ne propose pas ce paramètre.
Indiquer où recevoir les webhooks
Avec un lien vidéo accepté par l'API ou lors de la réservation d'un import par blocs, ajoutez webhook_url au JSON envoyé à POST /v1/transcriptions. Si vous envoyez le fichier en une seule requête, ajoutez plutôt ce paramètre au formulaire :
{
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"webhook_url": "https://example.com/hooks/transcript1"
}Utilisez une URL HTTPS, sans nom d'utilisateur ni mot de passe. Elle doit mener à une adresse publique : les adresses privées, loopback, link-local et toute autre adresse non publique sont refusées avec 422 invalid_request. L'API contrôle l'adresse à la création de la transcription, puis avant chaque envoi du webhook.
Vous ne pouvez pas utiliser webhook_url avec reuse_recent=true.
Événements
type | Quand le webhook est envoyé |
|---|---|
transcription.completed | Le statut passe à completed : le texte est prêt. |
transcription.failed | Le statut passe à failed. Vous trouverez le code et le message d'erreur dans data.error. |
Si vous annulez la transcription, aucun webhook n'est envoyé.
Lire le contenu du webhook
Le webhook contient un corps JSON :
{
"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
}
}Le champ created_at indique quand l'événement a été créé. Le bloc data reprend l'objet transcription, sans transcript ; l'exemple ci-dessus n'en montre qu'une partie. Pour récupérer le texte, appelez GET /v1/transcriptions/:id en utilisant l'id présent dans data.
Les en-têtes comprennent Content-Type: application/json, User-Agent avec la valeur transcript1-webhooks/1.0, et la signature décrite ci-dessous.
Vérifier la signature
Vous trouverez la signature de chaque requête dans X-Transcript1-Signature :
X-Transcript1-Signature: t=<unix seconds>,v1=<hex digest>tdonne l'instant de signature sous forme de timestamp Unix, en secondes.v1contient le HMAC-SHA256 en hexadécimal de la chaîne"<t>.<raw body>". Pour la construire, prenez la valeur det, ajoutez un point, puis le corps de la requête tel que vous l'avez reçu. Utilisez votre secret de signature complet comme clé, sans retirer le préfixewhsec_.
Calculez la signature à partir des octets bruts du corps, avant de parser le JSON. Si vous parsez puis sérialisez à nouveau le JSON, les espaces ou l'ordre des clés peuvent changer : la signature ne correspondra plus.
Comparez les signatures en temps constant. Vérifiez aussi l'écart entre t et l'heure actuelle, et refusez les requêtes trop éloignées dans le temps. Les exemples ci-dessous acceptent un écart de 5 minutes.
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));
};Avec Express, récupérez le corps en texte brut pour vérifier la signature avant de parser le JSON :
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)Avec Flask, récupérez le corps avec request.get_data(as_text=True) et transmettez-le dans raw_body.
Afficher ou renouveler le secret de signature
Le secret commence par whsec_. Il est commun à votre équipe et sert à signer les webhooks de toutes ses transcriptions. Pour l'afficher ou le renouveler, ouvrez la page « Webhooks » du tableau de bord, à l'adresse /dashboard/webhooks.
Le renouvellement prend effet immédiatement. Tous les webhooks envoyés ensuite utilisent le nouveau secret.
Délais, nouvelles tentatives et doublons
- Répondez avec un statut
2xxpour confirmer la réception. - Tout autre statut, une erreur de connexion ou l'absence de réponse sous 10 secondes fait échouer la tentative.
- Après un échec, transcript1 réessaie, dans la limite de 6 tentatives au total. Le délai double à chaque fois : 2, 4, 8, 16, puis 32 minutes. La dernière tentative a donc lieu environ une heure après la première.
- Donnez directement l'URL finale : transcript1 ne suit pas les redirections. Un statut
3xxcompte comme un échec. - Prévoyez les doublons. Un webhook peut arriver plusieurs fois, notamment si votre serveur répond après le délai d'attente. Appuyez-vous sur
data.idettypepour ne pas traiter deux fois le même événement.
Dernière mise à jour 14 octobre 2026