# Webhooks

Envía los datos del envío a cualquier endpoint HTTPS — tu backend, función serverless o proxy.

## Webhooks

Envía cada envío completado como una solicitud POST firmada a cualquier endpoint HTTPS: tu backend, plataforma de automatización o función serverless.

<h2 id="how-it-works">Cómo funciona</h2>

<p>
  Con cada envío, Formstep realiza un POST con un cuerpo JSON a tu URL de webhook. La solicitud está firmada con HMAC-SHA256, se reintenta
  en caso de fallo y se registra en el registro de eventos de la integración. Esta página cubre la configuración. El payload exacto, los
  encabezados y el algoritmo de firma están en la <a href="/es/developers/webhooks-reference">referencia de webhooks</a>.
</p>

<h2 id="add">Añadir un webhook</h2>

> ℹ️ **Múltiples webhooks**
> <p>Puedes asociar varios webhooks a un mismo formulario. Cada uno se activa de forma independiente para cada evento.</p>

<h2 id="url-rules">Qué URLs acepta Formstep</h2>

<ul>
  <li>
    <code>https://</code> en todos los casos, o <code>http://</code> para <code>localhost</code> y <code>*.localhost</code> durante el
    desarrollo.
  </li>
  <li>Sin credenciales en la URL, y como máximo 2.048 caracteres.</li>
  <li>
    Sin direcciones privadas o internas. El nombre de host se resuelve y se vuelve a comprobar justo antes de <em>cada</em> entrega, así que
    un registro DNS redirigido a una dirección interna después de la configuración sigue siendo rechazado.
  </li>
</ul>

<p>
  Una URL rechazada es un problema de configuración, no algo transitorio: la entrega falla de forma permanente en lugar de reintentarse.
</p>

<h2 id="payload">Qué recibes</h2>

<p>
  Cada solicitud es el mismo sobre de evento — <code>id</code>, <code>type</code>, <code>createdAt</code>, <code>apiVersion</code>,{' '}
  <code>test</code> y <code>data</code>. Dentro de <code>data</code> están el formulario, el envío (id, correo del encuestado, hora de
  envío, enlace del PDF, idioma), un objeto <code>answers</code> indexado por <a href="/es/requests/field-keys">clave de campo</a> y un
  objeto <code>display</code> con las mismas claves como texto legible. Cada respuesta aparece una vez, en cada mapa.
</p>

<p>
  Consulta la referencia para ver la <a href="/es/developers/webhooks-reference#payload">forma completa del payload</a>,{' '}
  <a href="/es/developers/webhooks-reference#fields-vs-answers">answers y display</a>, y cómo se representa un{' '}
  <a href="/es/building-forms/repeating-groups">grupo repetible</a>.
</p>

<p>
  Para previsualizar el cuerpo exacto de tu formulario, abre la integración y despliega <strong>Payload de ejemplo</strong> debajo del
  secreto de firma. Muestra tu mapeo actual con respuestas de ejemplo.
</p>

<h2 id="signatures">Verificar firmas</h2>

<p>
  Cada solicitud incluye un encabezado <code>X-Formstep-Signature</code>: <code>t=TIMESTAMP,sha256=HEX</code>, un HMAC-SHA256 de{' '}
  <code>TIMESTAMP.BODY</code> calculado con tu secreto de firma. El secreto en sí nunca se envía. La referencia tiene un{' '}
  <a href="/es/developers/webhooks-reference#signing">fragmento de verificación listo para copiar y pegar</a>.
</p>

> ⚠️ **Verifica siempre en producción**
> <p>
>     Sin verificación, cualquiera que descubra tu URL puede enviar envíos falsos. Rechaza las solicitudes en las que la firma falte o sea
>     inválida.
>   </p>

<h2 id="abandoned-responses">Eventos de respuestas abandonadas</h2>

<p>
  Los webhooks personalizados solo se activan para envíos completados y ediciones — nunca para borradores abandonados. Un webhook
  personalizado es el único receptor que recibe ambos: una suscripción de Zapier, Make o n8n elige envíos nuevos o ediciones, nunca ambos.
  Para borradores abandonados, usa un proveedor que tenga un paso <strong>Envíos abandonados</strong>: Google Sheets, Airtable, Notion,
  Slack, Discord, Linear o GitHub Issues. Cada uno tiene su propia ventana de inactividad y, cuando aplica, su propia plantilla de mensaje.
  Ese paso requiere Pro o Business.
</p>

<h2 id="retries">Reintentos y fallos</h2>

<ul>
  <li>
    Una entrega tiene éxito con cualquier <code>2xx</code>.
  </li>
  <li>
    Hasta 5 intentos: el primero se lanza de inmediato, los reintentos esperan al menos 1, 2, 4 y 8 minutos. Formstep busca reintentos
    pendientes cada 30 minutos, así que el último intento llega unas dos horas después del primero. Un encabezado <code>Retry-After</code>{' '}
    en un <code>429</code> o <code>5xx</code> se respeta cuando pide una espera más larga.
  </li>
  <li>
    <code>429</code>, <code>5xx</code>, los tiempos de espera agotados y los fallos de conexión se reintentan. Cualquier otro{' '}
    <code>4xx</code> falla de inmediato.
  </li>
  <li>
    Tras 5 fallos consecutivos, la integración se pausa automáticamente y la persona que la configuró recibe un correo. Soluciona el
    endpoint y luego pulsa <strong>Reanudar</strong>.
  </li>
  <li>
    <code>401</code>, <code>403</code> y <code>404</code> detienen la integración de inmediato con un estado de error y el mismo correo — no
    hay que esperar a los cinco fallos.
  </li>
  <li>
    Las entregas que agotan los 5 intentos se acumulan en un banner de la integración. <strong>Reintentar todo</strong> vuelve a encolarlas
    y reactiva una integración pausada.
  </li>
</ul>

<h2 id="testing">Pruebas</h2>

<p>
  <strong>Enviar un evento de prueba</strong> aparece en el paso Finaliza y también en la integración guardada. Hace POST de una respuesta
  de ejemplo sintetizada — <code>"John Doe"</code> para texto, <code>42</code> para números, <code>john@example.com</code> para email —
  firmada y con tus encabezados personalizados, igual que una entrega real. Desde la integración guardada también escribe una entrada de
  prueba de conexión en el registro de eventos.
</p>

<p>Para el desarrollo local, expón tu servidor de desarrollo con un túnel:</p>

```
# ngrok
ngrok http 3000

# cloudflare tunnel

cloudflared tunnel --url http://localhost:3000
```

<h2 id="faq">Preguntas frecuentes</h2>

  <p>
    Sí. Cada uno tiene su propia URL, secreto de firma y cabeceras personalizadas. Todos los webhooks activos se activan de forma
    independiente para cada envío.
  </p>

  <p>
    Sí — hasta 5, añadidas durante la configuración o después desde la integración. <code>Content-Type</code> se establece automáticamente y
    cualquier intento de sobrescribirlo se ignora.
  </p>

  <p>
    No. El secreto de firma se genera una sola vez al crear el webhook y no se puede cambiar. Si necesitas un nuevo secreto, elimina el
    webhook y crea uno nuevo.
  </p>

  <p>Sí. Abre la integración, edita la URL y guarda. El secreto de firma y el historial de eventos se mantienen.</p>

  <p>
    Solo para <code>localhost</code> y <code>*.localhost</code> durante el desarrollo. El resto de URLs deben usar HTTPS.
  </p>

  <p>
    Elimínala desde Configuración del formulario → Integraciones. Formstep deja de enviar solicitudes de inmediato, y el historial de
    eventos de la integración se elimina con ella.
  </p>

<h2 id="next-steps">Próximos pasos</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Referencia de webhooks](/es/developers/webhooks-reference) — Payload, encabezados y verificación de firmas
  - [Airtable](/es/integrations/airtable) — Envía envíos a una base de Airtable
  - [Linear](/es/integrations/linear) — Convierte envíos en issues de Linear
  - [Grupos repetibles](/es/building-forms/repeating-groups) — Permite que los encuestados añadan tantas entradas como necesiten
</div>
