Webhooks
En lugar de consultar un trabajo de IA hasta que termina, puedes indicar una webhook_url y CreaVibe te avisará con su resultado.
Cómo se usa
Añade webhook_url al crear el trabajo (POST /jobs). Debe ser una URL https pública. Cuando el trabajo termine, CreaVibe enviará un POST a esa URL con el trabajo completo, el mismo que devuelve Consultar un trabajo:
POST a tu webhook_url · cabeceras
Content-Type: application/json
webhook-id: 974c1661-3528-4099-9406-9688f1248f78.succeeded
webhook-timestamp: 1791799880
webhook-signature: v1,<HMAC-SHA256 en base64>POST a tu webhook_url · cuerpo
{
"type": "job.succeeded",
"timestamp": 1791799880,
"data": {
"id": "974c1661-3528-4099-9406-9688f1248f78",
"type": "generate_svg",
"status": "succeeded",
"style": null,
"asset": {
"id": "087f7237-8b00-4fd1-9e08-9736b2b754f7",
"url": "https://creavibe.app/media/recursos/017dfef2-eaf5-429a-86f3-307b8bf36189/ef7523e1cdc34bb98d3cde4b938d311d.svg",
"thumbnail_url": "https://creavibe.app/media/recursos/017dfef2-eaf5-429a-86f3-307b8bf36189/ef7523e1cdc34bb98d3cde4b938d311d.svg",
"type": "svg",
"source": "generated",
"name": "Coffee shop icons cup bean croissant",
"font_family": null,
"width": null,
"height": null,
"size_bytes": 3575,
"created_at": "2026-10-12T10:11:10.932Z"
},
"reply": null,
"error": null,
"credits_remaining": 15,
"webhook_url": null,
"created_at": "2026-10-12T10:10:32.074Z",
"updated_at": "2026-10-12T10:10:52.996Z"
}
}Eventos
type | Cuándo |
|---|---|
job.succeeded | El trabajo terminó: el resultado está en data.asset. |
job.failed | El trabajo falló: el motivo está en data.error.code y los créditos se han devuelto. |
Verificar la firma
Cada aviso va firmado según el estándar Standard Webhooks: un HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{cuerpo} con tu secreto. Comprueba la firma antes de fiarte del contenido y rechaza los avisos de hace más de 5 minutos.
Tu secreto (whsec_…) está en el menú de tu cuenta, en API → MCP y Webhook (entra en CreaVibe); desde ahí también puedes regenerarlo.
import crypto from 'node:crypto';
// secret: el de tu cuenta (whsec_…); body: el cuerpo tal cual llegó (texto, sin volver a serializarlo)
function verify(secret, headers, body) {
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const signed = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${body}`;
const expected = 'v1,' + crypto.createHmac('sha256', key).update(signed).digest('base64');
const fresh = Math.abs(Date.now() / 1000 - Number(headers['webhook-timestamp'])) < 300;
return fresh && headers['webhook-signature'].split(' ').some(sig =>
sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
}import base64, hashlib, hmac, time
# secret: el de tu cuenta (whsec_…); body: el cuerpo tal cual llegó (bytes decodificados, sin volver a serializarlo)
def verify(secret, headers, body):
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.{body}".encode()
expected = "v1," + base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
fresh = abs(time.time() - int(headers["webhook-timestamp"])) < 300
return fresh and any(hmac.compare_digest(sig, expected) for sig in headers["webhook-signature"].split())Entrega y reintentos
- Responde con cualquier código
2xxen menos de 10 segundos; haz el trabajo pesado después. - Si no responde o responde otra cosa, se reintenta hasta 6 veces con esperas crecientes (de 1 minuto a unas 17 horas).
- Un mismo aviso puede llegar más de una vez: usa
webhook-idpara no procesarlo dos veces. - No se siguen redirecciones.