Webhooks
Reciba un aviso firmado cuando termine una generación de la Direct API, verifíquelo y lea la generación para obtener sus detalles.
Un webhook es un endpoint HTTPS suyo al que Morphic llama cuando una generación alcanza un estado final. Las entregas van firmadas, se reintentan durante aproximadamente un día y quedan registradas. Cada evento es un aviso mínimo: indica qué generación terminó y cómo, y usted lee la generación con su clave para obtener sus recursos, su facturación y su mensaje de error.
Registrar un endpoint
POST /v1/direct/webhooks necesita el permiso 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"
}'| Campo | Reglas |
|---|---|
url | HTTPS en un host público (sin direcciones IP literales ni direcciones privadas o locales), hasta 2,048 caracteres |
events | uno o más tipos de evento |
description | opcional, hasta 200 caracteres |
La respuesta es 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_secretsolo se muestra en esta respuesta. Guárdelo como guardaría la clave de API.- Para rotar el secreto, registre un endpoint nuevo, cambie sus llamadas a su id y luego elimine el antiguo.
- Una organización puede tener hasta 10 endpoints.
Gestionar endpoints
| Solicitud | Permiso | Qué hace |
|---|---|---|
GET /v1/direct/webhooks | assets:read | enumera los endpoints como { "webhooks": [ … ] }, nunca con sus secretos |
DELETE /v1/direct/webhooks/{webhook_id} | generations:write | elimina el endpoint; las entregas aún pendientes hacia él se abandonan |
Asociar un endpoint a una llamada
Pase webhook_id en el cuerpo de la llamada a la herramienta. Cada generación que inicia esa llamada se entrega a ese endpoint cuando alcanza un estado final al que el endpoint está suscrito. Las llamadas sin webhook_id no envían webhooks.
Las herramientas que renderizan video en los servidores de Morphic (video_compose, las transformaciones video_* y burn_subtitles) todavía no aceptan webhook_id; sondee sus generaciones.
Tipos de evento
| Evento | Se envía cuando |
|---|---|
generation.succeeded | la generación terminó con salida |
generation.failed | la generación falló; su cobro se reembolsa |
generation.cancelled | la generación se canceló; su cobro se reembolsa |
- Cada generación produce como máximo un evento.
queuedein_progressno se entregan; si los necesita, obténgalos mediante sondeo.- Una generación se vigila durante 72 horas desde su inicio. Una que termine más tarde no envía ningún evento, así que léala directamente.
La entrega
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 solo lleva estos campos:
| Campo | Significado |
|---|---|
object, id, tool, status, completed_at, metadata | como en el objeto de generación |
error_code | null, o el código de una generación failed |
Un evento nunca lleva salidas, enlaces, facturación, el modelo ni el mensaje de error, de modo que una entrega que acabe en el lugar equivocado o en un registro no revela nada. Al recibirla, lea la generación con su clave: GET /v1/direct/generations/{id}, o la lectura por lotes para varias. Cuando se envía un evento ya no queda nada pendiente en su facturación, así que esa lectura es definitiva.
| Encabezado | Significado |
|---|---|
Morphic-Event-Id | el id del evento, idéntico en cada reintento; deduplique por él |
Morphic-Delivery-Id | este intento concreto; cambia en cada reintento |
Morphic-Event-Type | el type del cuerpo |
Morphic-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
Un evento suele enviarse en torno a un minuto después de que la generación alcance su estado final. No es un flujo en tiempo real: para la menor latencia en una generación concreta que esté esperando, use un sondeo largo.
Verificar la firma
v1 es el HMAC-SHA256 en hexadecimal, con el signing_secret de su endpoint como clave, de la cadena {t}.{raw request body}.
- Divida el encabezado por
,entyv1. - Calcule
HMAC_SHA256(secret, t + "." + raw_body)sobre los bytes sin procesar que recibió, antes de cualquier análisis o reserialización del JSON. - Compárelo con
v1en tiempo constante. - Rechace la entrega si
tse aleja más de 5 minutos de la hora actual, para impedir repeticiones.
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 endpoint sin verificar es una puerta abierta: cualquiera que conozca su URL puede decirle que una generación ha terminado. Verifique cada firma y rechace cualquier marca de tiempo con más de 5 minutos de antigüedad.
Respuestas y reintentos
- Responda con cualquier
2xxen un plazo de 10 segundos y después haga el trabajo. - Cualquier otra cosa es un intento fallido: un estado distinto de
2xx, un tiempo de espera agotado, una conexión rechazada, un error de TLS. - No se siguen las redirecciones; un
3xxes un intento fallido. - El orden no está garantizado y un evento puede llegar más de una vez. Haga que su controlador sea idempotente respecto a
Morphic-Event-Id, o adata.generation.id, ya que una generación tiene como máximo un evento.
Un intento fallido se reintenta tras:
| Reintento | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Espera desde el intento anterior | 1 min | 5 min | 15 min | 1 h | 3 h | 6 h | 12 h |
Tras el 7.º reintento, unas 22 horas después del primer intento, el evento se abandona. La generación en sí no se ve afectada: léala con GET /v1/direct/generations/{id}.
Registro de entregas
GET /v1/direct/webhooks/{webhook_id}/events (permiso assets:read) devuelve los 20 eventos más recientes del endpoint, cada uno con el cuerpo exacto enviado y el resultado de cada intento. Añada ?generation_id=… para ver los eventos de una sola generación.
{
"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 del evento | Significado |
|---|---|
pending | aún no entregado; puede haber más intentos |
delivered | su endpoint respondió 2xx |
abandoned | el último reintento falló, o se eliminó el endpoint |
skipped | el endpoint no estaba suscrito a ese tipo de evento |
Los webhooks son la señal más barata y más rápida, no una garantía. Mantenga un sondeo ocasional de las generaciones de las que no haya tenido noticias como red de seguridad.
Si tiene algún problema o alguna pregunta, no dude en ponerse en contacto con nosotros. Estaremos encantados de ayudarle. Escríbanos a support@morphic.com.
Generaciones
Siga una generación de la Direct API hasta su estado final con sondeo largo, lea varias a la vez y sepa cuándo sondear.
Créditos y uso
Cómo se cobran, liquidan y reembolsan las llamadas de la Direct API, cómo presupuestar un trabajo de antemano y cómo leer el gasto y el registro de su clave.