Webhooks
Recevez une notification signée lorsqu’une génération de la Direct API se termine, vérifiez-la, puis lisez la génération pour en obtenir le détail.
Un webhook est un point de terminaison HTTPS qui vous appartient et que Morphic appelle lorsqu’une génération atteint un statut final. Les livraisons sont signées, retentées pendant environ une journée et journalisées. Chaque événement est une notification minimale : il indique quelle génération s’est terminée et comment, et vous lisez la génération avec votre clé pour obtenir ses assets, sa facturation et son message d’erreur.
Enregistrer un point de terminaison
POST /v1/direct/webhooks nécessite l’autorisation generations:write.
curl -s https://mcp.morphic.com/v1/direct/webhooks \
-H "Authorization: Bearer $MORPHIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/morphic",
"events": ["generation.succeeded", "generation.failed", "generation.cancelled"],
"description": "Production order pipeline"
}'| Champ | Règles |
|---|---|
url | HTTPS sur un hôte public (pas d’adresse IP littérale, pas d’adresse privée ou locale), jusqu’à 2048 caractères |
events | un ou plusieurs types d’événements |
description | facultatif, jusqu’à 200 caractères |
La réponse est 201 :
{
"webhook": {
"id": "01929a10-0c3d-7f44-8a21-0b1c2d3e4f50",
"url": "https://hooks.example.com/morphic",
"events": ["generation.succeeded", "generation.failed", "generation.cancelled"],
"description": "Production order pipeline",
"created_at": "2026-10-25T08:00:00.000Z",
"signing_secret": "whsec_4b1e…"
}
}signing_secretn’est affiché que dans cette réponse. Conservez-le comme vous le feriez pour la clé d’API.- Pour renouveler le secret, enregistrez un nouveau point de terminaison, faites passer vos appels sur son identifiant, puis supprimez l’ancien.
- Une organisation peut détenir jusqu’à 10 points de terminaison.
Gérer les points de terminaison
| Requête | Autorisation | Rôle |
|---|---|---|
GET /v1/direct/webhooks | assets:read | liste les points de terminaison sous la forme { "webhooks": [ … ] }, jamais avec leurs secrets |
DELETE /v1/direct/webhooks/{webhook_id} | generations:write | supprime le point de terminaison ; les livraisons encore en attente pour celui-ci sont abandonnées |
Associer un point de terminaison à un appel
Transmettez webhook_id dans le corps de l’appel d’outil. Chaque génération lancée par cet appel est livrée à ce point de terminaison lorsqu’elle atteint un statut final auquel le point de terminaison est abonné. Les appels sans webhook_id n’envoient aucun webhook.
Les outils qui effectuent le rendu vidéo sur les serveurs de Morphic (video_compose, les transformations video_* et burn_subtitles) n’acceptent pas encore webhook_id ; interrogez leurs générations par sondage.
Types d’événements
| Événement | Envoyé lorsque |
|---|---|
generation.succeeded | la génération s’est terminée avec un résultat |
generation.failed | la génération a échoué ; le montant facturé est remboursé |
generation.cancelled | la génération a été annulée ; le montant facturé est remboursé |
- Chaque génération produit au plus un événement.
queuedetin_progressne sont pas livrés ; interrogez par sondage si vous en avez besoin.- Une génération est surveillée pendant 72 heures après son lancement. Une génération qui se termine plus tard n’envoie aucun événement : lisez-la plutôt.
La livraison
POST /morphic HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: Morphic-Webhooks/1.0
Morphic-Event-Id: 0192a7c0-5d4e-7b3a-8c2d-1e0f9a8b7c6d
Morphic-Delivery-Id: 0192a7c0-6e5f-7c4b-9d3e-2f1a0b9c8d7e
Morphic-Event-Type: generation.succeeded
Morphic-Signature: t=1792919741,v1=5f8a0c…e21b
{
"id": "0192a7c0-5d4e-7b3a-8c2d-1e0f9a8b7c6d",
"type": "generation.succeeded",
"created_at": "2026-10-25T09:15:42.410Z",
"api_version": "v1",
"data": {
"generation": {
"object": "generation",
"id": "0192a7b3-a1c4-7e02-8f9d-1a2b3c4d5e6f",
"tool": "generate_video",
"status": "succeeded",
"completed_at": "2026-10-25T09:15:41.902Z",
"error_code": null,
"metadata": { "order_id": "4411" }
}
}
}data.generation ne contient que ces champs :
| Champ | Signification |
|---|---|
object, id, tool, status, completed_at, metadata | comme dans l’objet génération |
error_code | null, ou le code d’une génération en failed |
Un événement ne contient jamais de résultats, de liens, de facturation, de modèle ni de message d’erreur : une livraison qui arrive au mauvais endroit ou dans un journal ne révèle donc rien. À la réception, lisez la génération avec votre clé : GET /v1/direct/generations/{id}, ou la lecture groupée pour plusieurs. Au moment où un événement est envoyé, plus rien n’est attendu sur sa facturation : cette lecture est donc définitive.
| En-tête | Signification |
|---|---|
Morphic-Event-Id | l’identifiant de l’événement, identique à chaque nouvelle tentative ; dédupliquez sur cette valeur |
Morphic-Delivery-Id | cette tentative précise ; change à chaque nouvelle tentative |
Morphic-Event-Type | le type du corps |
Morphic-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
Un événement est généralement envoyé dans la minute environ qui suit l’arrivée de la génération à son statut final. Ce n’est pas un flux en temps réel : pour la latence la plus faible sur une génération que vous attendez, interrogez-la en long-polling.
Vérifier la signature
v1 est le HMAC-SHA256 en hexadécimal, avec pour clé le signing_secret de votre point de terminaison, de la chaîne {t}.{raw request body}.
- Découpez l’en-tête sur
,pour obtenirtetv1. - Calculez
HMAC_SHA256(secret, t + "." + raw_body)sur les octets bruts reçus, avant toute analyse ou resérialisation JSON. - Comparez-le à
v1en temps constant. - Rejetez la livraison si
ts’écarte de plus de 5 minutes de l’heure actuelle, pour empêcher les rejeux.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/morphic', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('Morphic-Signature') ?? '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', process.env.MORPHIC_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
const valid =
parts.v1?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
enqueue(event); // process asynchronously, keyed by event.id
res.sendStatus(204);
});Un point de terminaison non vérifié est une porte ouverte : quiconque connaît son URL peut vous faire croire qu’une génération s’est terminée. Vérifiez chaque signature et rejetez tout horodatage de plus de 5 minutes.
Réponse et nouvelles tentatives
- Répondez par n’importe quel
2xxdans les 10 secondes, puis effectuez le traitement. - Toute autre issue est une tentative échouée : un statut autre que
2xx, un délai dépassé, une connexion refusée, une erreur TLS. - Les redirections ne sont pas suivies ; un
3xxest une tentative échouée. - L’ordre n’est pas garanti et un événement peut arriver plusieurs fois. Rendez votre gestionnaire idempotent sur
Morphic-Event-Id, ou surdata.generation.id, puisqu’une génération a au plus un événement.
Une tentative échouée est retentée après :
| Nouvelle tentative | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Délai après la tentative précédente | 1 min | 5 min | 15 min | 1 h | 3 h | 6 h | 12 h |
Après la 7e nouvelle tentative, environ 22 heures après la première tentative, l’événement est abandonné. La génération elle-même n’est pas affectée : lisez-la avec GET /v1/direct/generations/{id}.
Journal des livraisons
GET /v1/direct/webhooks/{webhook_id}/events (autorisation assets:read) renvoie les 20 événements les plus récents du point de terminaison, chacun avec le corps exact envoyé et l’issue de chaque tentative. Ajoutez ?generation_id=… pour obtenir les événements d’une seule génération.
{
"webhook_events": [
{
"id": "0192a7c0-5d4e-…",
"type": "generation.succeeded",
"generation_id": "0192a7b3-…",
"status": "delivered",
"created_at": "2026-10-25T09:15:42.410Z",
"payload": { "…": "the body that was sent" },
"attempts": [
{ "id": "0192a7c0-6e5f-…", "attempted_at": "2026-10-25T09:15:42.900Z", "response_status": 204, "duration_ms": 131, "error": null, "response_body_excerpt": null }
]
}
]
}status de l’événement | Signification |
|---|---|
pending | pas encore livré ; d’autres tentatives peuvent suivre |
delivered | votre point de terminaison a répondu 2xx |
abandoned | la dernière tentative a échoué, ou le point de terminaison a été supprimé |
skipped | le point de terminaison n’était pas abonné à ce type d’événement |
Les webhooks sont le signal le moins coûteux et le plus rapide, pas une garantie. Conservez un sondage occasionnel des générations dont vous n’avez pas eu de nouvelles, comme filet de sécurité.
Si vous rencontrez le moindre problème ou si vous avez des questions, n’hésitez pas à nous contacter. Nous serons ravis de vous aider. Écrivez-nous à support@morphic.com.
Générations
Suivez une génération de la Direct API jusqu’à son statut final par long-polling, lisez-en plusieurs à la fois et sachez à quel rythme interroger.
Crédits et consommation
Comment les appels de la Direct API sont facturés, régularisés et remboursés, comment obtenir un devis avant de lancer un travail, et comment lire les dépenses et le registre de votre clé.