---
title: "Usar datos de la aplicación en una secuencia de voz"
description: "Solicita datos tipados a un receptor HTTPS durante una llamada, ramifica según ellos en un paso posterior, gestiona explícitamente los fallos técnicos y mantén las respuestas privadas separadas de los datos de llamada conservados."
canonical: "https://bird.com/es-es/documentacion/guides/voice/sequences/code/webhooks"
---

# Usar datos de la aplicación en una secuencia de voz

Usa `voice.webhook` cuando tu aplicación tenga datos que la llamada necesita: el estado de un pedido, un equipo o la confirmación de una cita. El paso devuelve esos datos en `success`, y un paso Branch posterior elige la ruta a partir de ellos.

## Definir la respuesta

Asigna a `config.output_schema` un esquema de objeto para los datos devueltos. El paso tiene dos salidas: `success` lleva datos con ese esquema y `failure` gestiona los problemas técnicos. Si omites la configuración, la salida es un objeto vacío.

La entrada contiene un `url` HTTPS estático y un objeto `data` opcional. Los datos admiten un objeto literal, `$literal` o un `$expr` de objeto completo. No se admiten URL dinámicas, cabeceras personalizadas ni credenciales.

## Verificar las solicitudes

Bird firma cada solicitud, incluidas las continuaciones, con el secreto de callback de tu espacio de trabajo mediante las cabeceras de [Standard Webhooks](https://www.standardwebhooks.com/): `webhook-id`, `webhook-timestamp` y `webhook-signature`. La firma cubre el ID, la marca de tiempo y el cuerpo exacto. Durante las 24 horas posteriores a la rotación de un secreto, `webhook-signature` lleva una entrada `v1,` por cada secreto activo, así que cualquiera de los dos secretos sirve para verificar. Las llamadas pueden tardar alrededor de un minuto en empezar a firmar con el secreto nuevo, así que sigue aceptando el anterior hasta que termine el periodo de solapamiento. Tu respuesta no se firma: Bird valida el certificado TLS de tu receptor, y la conexión HTTPS autentica lo que devuelve.

Verifica las cabeceras sobre el cuerpo sin procesar con la llamada verify de una biblioteca de Standard Webhooks antes de analizarlo. El `webhooks.unwrap` de los SDK analiza eventos de la plataforma y no sirve para estas solicitudes. Primero rota el secreto en **Webhooks → Direct callbacks**, porque el inicial nunca se muestra. La firma demuestra que alguien que tiene el secreto de tu espacio de trabajo firmó la solicitud. Tus receptores de callback SMS tienen el mismo secreto y también pueden firmar, así que guárdalo solo en receptores de confianza. Cualquiera que pueda editar las secuencias del espacio de trabajo elige la URL y `data`. Comprueba que `target_url` es tu propia URL para rechazar una solicitud que Bird firmó para otro receptor, y autoriza las acciones de negocio en tu receptor. La autorización del cliente nunca se reenvía.

Las solicitudes de webhook de las secuencias no presentan un certificado de cliente, aunque tu espacio de trabajo exija mTLS. Configura el receptor para que acepte conexiones sin certificado de cliente y autentica la solicitud firmada en su lugar.

Compara el `webhook-timestamp` firmado, una marca de tiempo Unix en segundos, con el reloj sincronizado de un servidor. Rechaza las solicitudes con más de cinco minutos de diferencia en el pasado o en el futuro. Elimina duplicados por `webhook-id` antes de ejecutar efectos secundarios. Siempre que puedas, guarda el ID y el resultado de forma atómica junto con la acción, y devuelve el resultado guardado si la solicitud se repite. Cada continuación tiene su propio ID. Usa una clave de idempotencia de negocio aparte si solicitudes distintas no deben repetir la misma acción.

## Devolver datos

El receptor devuelve esta estructura de respuesta cerrada:

```json
{
  "schema_version": 1,
  "type": "outcome",
  "output": { "status": "confirmed", "message": "Your appointment is confirmed." }
}
```

`output` debe coincidir con el esquema configurado. La respuesta devuelve datos, no un puerto, un ID de nodo ni comandos nuevos. Una continuación es `{"schema_version":1,"type":"continue","url":"https://example.com/voice/next"}` en el origen canónico de la URL original.

Para enrutar según los datos, conecta `success` a un paso `logic.branch` con casos como `steps.decision.output.status == 'confirmed'`. Declara el campo como una cadena `enum` en el esquema para que el receptor solo pueda devolver esos valores.

La solicitud contiene la identidad de la ejecución, la sesión y la llamada, la identidad de la secuencia admitida y del nodo, tu `data` materializado y `results`. Mantén los datos de negocio en `data`. Un resultado privado directo de Gather puede aparecer una vez en `results`, y las grabaciones finalizadas incluyen un ID cuando está disponible. Estas observaciones no se convierten automáticamente en campos de expresión.

## Leer la solicitud del receptor

Una ejecución de prueba de un borrador envía esta estructura. Los ID y las marcas de tiempo siguientes son ficticios; la solicitud real usa la ejecución admitida y la ocurrencia actual del nodo:

```json
{
  "schema_version": 1,
  "method": "POST",
  "target_url": "https://example.com/decision",
  "run_id": "vsr_01k5yb7p000000000000000000",
  "sequence": {
    "id": "vsq_01k5yb7p000000000000000000",
    "version_id": null,
    "draft_revision": 1,
    "entry_node_id": "start",
    "mode": "test"
  },
  "session_id": "vcs_01k5yb7p000000000000000000",
  "call": { "id": "vcl_01k5yb7p000000000000000000", "orig": null, "dest": null },
  "node": { "id": "decision", "occurrence_id": "00000000-0000-4000-8000-000000000001" },
  "data": { "appointment_id": "appointment_123" },
  "results": []
}
```

En una ejecución publicada, `version_id` identifica la versión seleccionada y `draft_revision` es null. En una llamada que ejecutó una [definición en línea](/docs/guides/voice/create-calls), `id`, `version_id` y `draft_revision` son todos null. `mode` puede ser `test` o `production`. El origen y el destino usan objetos de participante de llamada que admiten null. Verifica la firma antes de confiar en estos campos de identidad.

Una observación de Gather en `results` tiene `type: "gather"`, `node_id`, el UUID `command_id`, `digits` y `reason`. Una observación de grabación tiene `type: "recording"`, el UUID `command_id`, `state` y un `recording_id` cuando está disponible. Usa los dígitos privados solo para la solicitud prevista; no se convierten en salidas de expresión conservadas. Las solicitudes de continuación mantienen la misma ocurrencia del nodo y eliminan de `results` las observaciones ya entregadas.

## Gestionar los fallos

Un fallo técnico usa el puerto `failure` con `{"code":"webhook_unavailable"}` o `{"code":"webhook_invalid_response"}`. Si el puerto de fallo está conectado, la ejecución sigue la ruta de recuperación; si no lo está, la ejecución falla. Un `success` sin conectar finaliza la ejecución con normalidad. Las solicitudes de webhook en vivo tienen un plazo de tres segundos y no se reintentan automáticamente. La vista previa no envía ninguna solicitud.

El plazo de tres segundos incluye la lectura del secreto de firma. Conecta `failure` a una ruta de recuperación para que la llamada pueda continuar cuando el receptor no esté disponible o devuelva una respuesta no válida. Una solicitud que agotó el tiempo de espera puede haber provocado ya un efecto secundario en el receptor.

Trata `data`, la identidad de la llamada y los dígitos de Gather como datos privados. Restringe el acceso al receptor y al secreto, evita registrar cuerpos o secretos y conserva la entrada de la persona que llama solo el tiempo que tu aplicación la necesite. No pongas datos privados ni credenciales en las URL: el registro de la llamada conserva la URL completa, incluida su consulta. La URL y los datos configurados y la salida validada se conservan en el historial de ejecuciones; los dígitos transitorios de Gather se excluyen de la entrada capturada del paso. Devuelve solo los datos que la secuencia necesita.

Lee los datos devueltos en `steps.decision.output` y los errores técnicos en `steps.decision.error.code`. Si ambas rutas se unen, comprueba el contenedor con `has(steps.decision.output)` o `has(steps.decision.error)` antes de usarlo. Un HTTP 200 de tu receptor no garantiza un resultado válido hasta que su cuerpo cumpla el contrato.

## Related resources

- [Voice sequences](/docs/guides/voice/sequence-builder) (docs)
