---
title: Devoluciones de llamada | RunAPI
description: Reciba y verifique de forma segura las entregas de devoluciones de llamada
  de Tasks.
url: https://runapi.ai/es/docs/guides/task-api/callbacks.md
canonical: https://runapi.ai/es/docs/guides/task-api/callbacks
locale: es
---

> Versión HTML: https://runapi.ai/es/docs/guides/task-api/callbacks
> Índice del sitio para agentes: https://runapi.ai/llms.txt

# Devoluciones de llamada

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](https://runapi.ai/es/docs/guides/authentication.md) 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](https://runapi.ai/es/docs/guides/task-api/quickstart.md).

---

## Más de RunAPI

- [Inicio](https://runapi.ai/es/.md)
- [Catálogo de modelos](https://runapi.ai/es/models.md)
- [Precios](https://runapi.ai/es/pricing.md)
- [Proveedores](https://runapi.ai/es/models)
- [Documentación](https://runapi.ai/es/docs/guides)
- [SDKs](https://runapi.ai/es/sdk.md)
- [CLI](https://runapi.ai/es/cli.md)
- [Servidor MCP](https://runapi.ai/es/mcp.md)
- [Claude Code vs Cursor](https://runapi.ai/es/claude-code-vs-cursor.md)
- [Configuración de Cursor API](https://runapi.ai/es/cursor-api-setup.md)
- [RunAPI vs OpenRouter](https://runapi.ai/es/openrouter-alternative.md)
- [Enterprise](https://runapi.ai/es/contact.md)
- [Contacto](https://runapi.ai/es/contact.md)
- [Términos](https://runapi.ai/es/terms.md)
- [Privacidad](https://runapi.ai/es/privacy.md)
- [Índice del sitio para agentes](https://runapi.ai/llms.txt)

Contacto: contact@runapi.ai

## Datos estructurados

```json
[
  {
    "@context": "https://schema.org",
    "inLanguage": "es",
    "@type": "WebSite",
    "name": "RunAPI",
    "url": "https://runapi.ai/es",
    "potentialAction": {
      "@type": "SearchAction",
      "target": {
        "@type": "EntryPoint",
        "urlTemplate": "https://runapi.ai/es/models?q={search_term_string}"
      },
      "query-input": "required name=search_term_string"
    }
  },
  {
    "@context": "https://schema.org",
    "inLanguage": "es",
    "@type": "Organization",
    "name": "RunAPI",
    "url": "https://runapi.ai/es",
    "logo": {
      "@type": "ImageObject",
      "url": "https://runapi.ai/esicon.svg"
    },
    "sameAs": [
      "https://github.com/runapi-ai"
    ]
  },
  {
    "@context": "https://schema.org",
    "inLanguage": "es",
    "@type": "TechArticle",
    "headline": "Devoluciones de llamada",
    "description": "Reciba y verifique de forma segura las entregas de devoluciones de llamada de Tasks.",
    "url": "https://runapi.ai/es/docs/guides/task-api/callbacks",
    "mainEntityOfPage": "https://runapi.ai/es/docs/guides/task-api/callbacks"
  }
]
```
