Saltar al contenido
Guías
Guías

Devoluciones de llamada

Reciba y verifique de forma segura las entregas de devoluciones de llamada de Tasks.

Usa devoluciones de llamada para recibir eventos del ciclo de vida de una Task en un endpoint HTTPS público. Verifica cada devolución de llamada antes de procesar su cuerpo JSON para que solo las entregas firmadas para tu cuenta puedan cambiar el estado de la aplicación.

Configurar una URL de devolución de llamada

Agrega una callback_url HTTPS pública cuando crees una tarea. El cuerpo del evento y los estados del ciclo de vida dependen del endpoint; consulta la Referencia de la API de ese endpoint para conocer sus cargas útiles de devolución de llamada.

JSON
{
  "model": "flux-2-pro-text-to-image",
  "prompt": "A product photograph on a clean studio background",
  "callback_url": "https://your-domain.com/webhooks/runapi"
}

Crear un Callback Secret

Siga la Guía de Autenticación para iniciar sesión, luego abra Claves de API y cree una clave secreta de devolución de llamada para la cuenta que crea la tarea. Guarde el valor en un administrador de secretos y hágalo disponible solamente para su receptor de devoluciones de llamada. No es una clave de API y nunca debe enviarse en una solicitud de tarea.

El secreto firma las devoluciones de llamada para esa cuenta. Rotarlo cambia la firma de las entregas posteriores, por lo que debes actualizar todos los receptores de devoluciones de llamada de inmediato y mantener ambos valores disponibles solo el tiempo suficiente para gestionar las entregas en curso.

Verificar una firma de devolución de llamada

Cada devolución de llamada es un HTTP POST con Content-Type: application/json. RunAPI no agrega un encabezado Authorization a esta solicitud. Verifique estos encabezados antes de deserializar el cuerpo:

Encabezado Significado X-Callback-Id Identificador único para este intento de entrega. X-Callback-Timestamp Marca de tiempo Unix en segundos en el momento en que se firmó la entrega. X-Callback-Signature Firma HMAC-SHA-256 codificada en Base64.

Construya el valor firmado exactamente de la siguiente manera, utilizando los bytes del cuerpo de la solicitud sin modificar:

TEXT
X-Callback-Id + "." + X-Callback-Timestamp + "." + raw request body

Decodifique en Base64 el Callback Secret, calcule HMAC-SHA-256 sobre ese valor, codifique el resultado en Base64 y compárelo con X-Callback-Signature usando una comparación segura en tiempo. No analice ni vuelva a serializar el JSON antes de verificarlo.

JavaScript

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

export function verifyCallback({headers, rawBody, callbackSecret}) {
  const callbackId = headers["x-callback-id"];
  const timestamp = Number(headers["x-callback-timestamp"]);
  const signature = headers["x-callback-signature"];

  if (!callbackId || !signature || !Number.isSafeInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const signedContent = `${callbackId}.${timestamp}.${rawBody}`;
  const expected = createHmac("sha256", Buffer.from(callbackSecret, "base64"))
    .update(signedContent, "utf8")
    .digest();
  const received = Buffer.from(signature, "base64");

  return expected.length === received.length && timingSafeEqual(expected, received);
}

Pase la cadena del cuerpo de la solicitud sin procesar del framework a rawBody; no llame a JSON.stringify sobre datos ya analizados.

Python

PYTHON
import base64
import hashlib
import hmac
import time


def verify_callback(headers, raw_body, callback_secret):
    callback_id = headers.get("X-Callback-Id")
    timestamp = headers.get("X-Callback-Timestamp")
    signature = headers.get("X-Callback-Signature")

    if not callback_id or not timestamp or not signature:
        return False

    try:
        timestamp = int(timestamp)
        secret = base64.b64decode(callback_secret, validate=True)
        received = base64.b64decode(signature, validate=True)
    except (ValueError, TypeError):
        return False

    if abs(time.time() - timestamp) > 300:
        return False

    signed_content = f"{callback_id}.{timestamp}.{raw_body}".encode("utf-8")
    expected = hmac.new(secret, signed_content, hashlib.sha256).digest()
    return hmac.compare_digest(expected, received)

Pase la cadena exacta del cuerpo de la solicitud sin procesar recibida por su framework HTTP como raw_body.

Procesar entregas de forma segura

  • Devuelva una respuesta 2xx solo después de que la devolución de llamada haya sido aceptada para su procesamiento. Tras una respuesta que no sea 2xx o un error de transporte, se intenta la entrega hasta 10 veces.
  • Responda en un plazo de 15 segundos. Encole el trabajo más lento después de la verificación en lugar de bloquear la respuesta HTTP.
  • Rechace las devoluciones de llamada con encabezados de firma ausentes, una firma no válida o una marca de tiempo fuera de la ventana de tolerancia que defina su receptor.

Solución de problemas de verificación

  • Discrepancia de firma: confirma que el secreto de devolución de llamada pertenece a la misma cuenta que la Tarea, decodifícalo con Base64 y firma el cuerpo original en lugar del JSON analizado.
  • Encabezados de firma ausentes: crea un secreto de devolución de llamada antes de depender de las devoluciones de llamada para cambios de estado.
  • Marca de tiempo rechazada: sincroniza el reloj del receptor y usa una tolerancia adecuada para tu despliegue.

Para obtener orientación sobre la configuración y el ciclo de vida, vuelva al Inicio rápido de la API de Task.