Ir al contenido principal

Guía de implementación

Terminar el trabajo cuando la petición ya no existe.

Una tarea de medios sobrevive a la llamada HTTP que la creó, a la página que la lanzó y a menudo al despliegue que estaba en marcha. Todo lo que sigue sirve para que eso no duela.

El reto

La conexión no es la transacción

Entre pulsar enviar y tener el archivo hay cuatro sitios donde perder trabajo: una creación reenviada a ciegas tras un tiempo agotado, una devolución que no puedes verificar, un bucle de sondeo que machaca o se rinde, y una URL de resultado caducada mientras decidías qué hacer.

01

Un reenvío sin clave es una segunda factura

Idempotency-Key hace que repetir el mismo modelo, entrada y devolución en menos de 24 horas devuelva la tarea original sin reservar fondos dos veces. Su ámbito es una sola clave de API; una clave hermana que repita el mismo valor recibe 409 sin identificador de tarea.

02

Una devolución sin verificar es un endpoint abierto

Las entregas llevan X-Webhook-Timestamp y un HMAC-SHA256 en Base64 dentro de X-Webhook-Signature, calculado sobre taskId.timestamp.sha256(raw_body). Verifica contra los bytes crudos en tiempo constante y rechaza marcas de tiempo antiguas antes de tocar el estado de la tarea o del cobro.

03

Sondear sin techo es pedir un límite de tasa

recordInfo es un respaldo, no un transporte. Empieza en unos dos segundos, crece un cincuenta por ciento, tope en quince y párate en un plazo que hayas elegido tú.

El enfoque

Un registro duradero por tarea, escrito antes de responder

Persiste la clave de idempotencia, el taskId y tu propio identificador de correlación en la misma transacción con la que respondes a quien llama. Cada evento posterior —devolución, sondeo, reintento, consulta de soporte— se resuelve contra esa fila, y una entrega duplicada se convierte en una actualización y no en un segundo pedido.

Verifica primero y lee después. La firma cubre los bytes crudos, así que se analiza tras la comparación.

Verify the callback before you trust it

import { createHash, createHmac, timingSafeEqual } from "node:crypto";

// rawBody is the exact bytes that arrived. Parse only to read the task ID:
// the digest is always computed over those raw bytes, never over a re-encode.
export function verifyDelivery(rawBody, headers, webhookKey) {
  const timestamp = headers["x-webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const taskId = JSON.parse(rawBody).data.taskId;          // payload version 2
  const digest = createHash("sha256").update(rawBody).digest("hex");
  const expected = createHmac("sha256", webhookKey)
    .update(`${taskId}.${timestamp}.${digest}`)
    .digest("base64");

  const sent = Buffer.from(headers["x-webhook-signature"] ?? "", "utf8");
  const mine = Buffer.from(expected, "utf8");
  return sent.length === mine.length && timingSafeEqual(sent, mine);
}

Delivery headers

X-Webhook-Timestamp: 1788652800
X-Webhook-Signature: <base64>
X-Webhook-Payload-Version: 2

{
  "code": 200,
  "msg": "success",
  "request_id": "whd_01k3m8x9q2z4v7n5p6r8s0t1w3",
  "data": { "taskId": "job_01k3…", "state": "succeeded", "settled": true, … }
}

Contrato

Lo que conviene fijar desde el principio

API de medios asíncrona: tareas y webhooks — Lo que conviene fijar desde el principio
Campo o cabeceraValor o límite
Idempotency-Key24 h · scoped to one API key
X-Webhook-Signaturebase64(HMAC-SHA256)
signed stringtaskId.timestamp.sha256(raw_body)
X-Webhook-Payload-Version2 for new integrations

Desliza horizontalmente para ver todas las columnas.

Integración

De la primera llamada a producción

  1. 01Genera y guarda una clave de idempotencia y llama a createTask con ella y con callBackUrl en un endpoint HTTPS por el puerto 80 o 443.
  2. 02Escribe data.taskId en tu base de datos antes de responder; el 202 es el único sitio donde aparece primero.
  3. 03Acepta la devolución, verifica la firma sobre el cuerpo crudo, deduplica por request_id y actualiza tu registro.
  4. 04Deja recordInfo con retroceso exponencial como respaldo y copia output.assets antes de que caduque la URL firmada.

Encaje

Cuándo este camino es el adecuado

Elígelo si

  • Cualquier cosa que produzca medios. Los endpoints de imagen, vídeo y audio son asíncronos por construcción.
  • Necesitas constancia duradera de qué se envió, cuánto se esperaba que costara y cómo terminó.

Busca otra vía si

  • Estás llamando a un modelo de texto: ese transmite dentro de la petición y se liquida por uso medido.
  • Quieres que el navegador sea dueño del trabajo. Las claves son del servidor, y el registro de la tarea también.

Comprobaciones

Lo que puedes verificar tú mismo

Toda respuesta lleva un request_id

El sobre es {code, msg, data, request_id} en todas las operaciones de /api/v1. En la versión 2 de carga útil, el request_id de la entrega se mantiene estable entre reenvíos, así que es la clave natural de deduplicación.

Las reservas se ven en el registro

cost con settled: false es la estimación reservada; settled: true es el cargo final. El éxito nunca supera la reserva y el fallo o la caducidad la liberan por completo.

Los fallos usan un vocabulario cerrado

errorCode es uno de nueve valores estables: invalid_request, unsupported_combination, content_rejected, rate_limited, upstream_unavailable, generation_failed, timeout, invalid_asset, upstream_failed. Cualquier otro valor se trata como upstream_failed.

Antes del lanzamiento

Comportamientos de la plataforma que conviene confirmar

  • Guarda la clave de idempotencia con la tarea, no en la memoria del proceso.
  • Haz idempotente el manejador de la devolución; las entregas se reintentan por diseño.
  • Fija un plazo de sondeo y avisa cuando una tarea lo supere.
  • Copia los archivos antes de expiresAt y vuelve a pedir la URL en lugar de cachear una firmada.

Preguntas frecuentes

Lo que más se pregunta en esta página

Reenvía la misma petición con la misma Idempotency-Key. Dentro de 24 horas y con la misma clave de API recuperas la tarea original sin una segunda reserva. Nunca envíes un cuerpo nuevo tras un tiempo agotado: eso es una tarea nueva y se cobra aparte.

Siguiente paso

Construir un flujo de tareas que sobreviva a un despliegue

Envía trabajos largos desde tu servidor y termínalos con garantías: claves de idempotencia, devoluciones firmadas, sondeo acotado y cinco estados.

Guías relacionadas