---
title: "Inserta el widget web"
description: "Añade un agente de voz a tu sitio de marketing o soporte con una sola etiqueta de script; quienes lo visitan hablan con él directamente desde el navegador, sin necesidad de número de teléfono."
---

El widget web ofrece a los visitantes de tu sitio una conversación de clic para hablar
con un agente de IA, usando el micrófono del navegador. Es un SDK independiente
de JavaScript / React con su propia [referencia del SDK](/es/widget/overview)
— esta guía se centra en la configuración de ThunderPhone que necesita el widget.

<Note>
  Puedes hacer todo esto sin cURL: la página **Widgets web** del panel
  (`/dashboard/web-widgets`) crea el widget, configura su modo
  y agente, administra los dominios permitidos y te proporciona el fragmento de inserción.
</Note>

## Requisitos previos

<Steps>
  <Step title="Crea un agente">
    El agente cuyo prompt y voz ejecutarán la sesión del widget. Configura
    `widget_enabled: true` (el valor predeterminado).
  </Step>
  <Step title="Decide el modo de enrutamiento">
    - `mode="agent"` — un agente estático por clave. El más simple.
    - `mode="webhook"` — tu servidor selecciona el agente por visitante mediante un
      [webhook `web.incoming`](/es/webhooks/call-incoming). Úsalo para usuarios
      que iniciaron sesión, pruebas A/B o enrutamiento por página.
  </Step>
  <Step title="Enumera los dominios permitidos">
    Indica cada nombre de host que incorporará el widget. Los navegadores envían el
    origen de la página con cada solicitud de sesión, por lo que la lista evita que tu clave
    funcione cuando alguien la copia en otro sitio. No es autenticación: un script fuera de un
    navegador puede enviar cualquier encabezado `Origin`. `localhost` no se permite automáticamente en producción; para
    realizar pruebas locales con la API de producción, agrega `localhost` a la
    lista.
  </Step>
  <Step title="Elige las anulaciones del navegador">
    Decide qué configuraciones de sesión puede enviar tu página. Las claves nuevas no aceptan
    ninguna; consulta [Anulaciones del navegador](#browser-overrides).
  </Step>
</Steps>

## Crea una clave publicable

<CodeGroup>
```bash Static agent
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Marketing site (prod)",
    "mode":            "agent",
    "agent_id":        12,
    "allowed_domains": ["example.com", "*.example.com"],
    "allowed_overrides": ["context"]
  }'
```

```bash Dynamic via webhook
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Support (dynamic)",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-hook",
    "allowed_domains": ["support.example.com"]
  }'
```
</CodeGroup>

La respuesta incluye una `key` que comienza con `pk_live_...`. **Las claves publicables
son públicas por diseño** — puedes incluirlas de forma segura en tu paquete de front-end.
Consulta la [referencia de claves publicables](/api-reference/publishable-keys) para
ver todos los campos.

<Warning>
  `allowed_domains` debe contener al menos una entrada. `*.example.com`
  coincide con subdominios (por ejemplo, `api.example.com`), pero **no** con
  el dominio raíz. Se rechazan los comodines generales como `*` o `*.*`.
</Warning>

## Añade el widget a tu sitio

La [documentación del SDK del widget](/es/widget/overview) cubre tres opciones
de integración:

<CardGroup cols={3}>
  <Card title="Componente de React" icon="react" href="/es/widget/react">
    `<ThunderPhoneWidget publishableKey="pk_live_..." />`.
  </Card>
  <Card title="Hook sin interfaz" icon="circle-nodes" href="/es/widget/headless-hook">
    `useThunderPhone()` para interfaces personalizadas.
  </Card>
  <Card title="Etiqueta script de CDN" icon="code" href="/es/widget/cdn-script-tag">
    `ThunderPhone.mount({...})` para sitios sin empaquetador.
  </Card>
</CardGroup>

Las tres aceptan la misma `publishableKey` y muestran el botón del micrófono
más el elemento de audio durante la llamada.

## Anulaciones del navegador

El widget puede enviar cuatro configuraciones por sesión desde la página:

| Configuración | Prop del widget | Qué cambia |
|---------|-------------|-----------------|
| `context` | `context` | Texto de la página, añadido al prompt del agente como referencia |
| `variables` | ninguna (solo `POST /v1/widget/session` sin procesar) | Valores para los marcadores `{{variable}}` en el prompt |
| `voice` | `voice` | La voz del agente para esta sesión |
| `language` | `language` | El idioma en el que comienza la sesión |

Tu clave publicable es pública, por lo que cualquiera que la tenga puede enviar estas
configuraciones, incluso desde un script que suplante tu dominio. Por lo tanto, cada una está
desactivada hasta que la habilites en la clave: selecciónala en **Anulaciones del navegador**
cuando crees o edites el widget en el panel, o configura
`allowed_overrides` mediante la
[API de claves publicables](/api-reference/publishable-keys). Permite solo lo que
realmente envía tu página.

Una solicitud de sesión que incluya una configuración que la clave no permite falla
con `400` y la indica:

```json
{
  "error": "override_not_allowed",
  "fields": ["voice"],
  "message": "This publishable key does not allow the browser to set `voice`. ..."
}
```

La verificación se aplica en ambos modos. En el modo webhook, una configuración permitida
también se reenvía a tu webhook `web.incoming` (`context` llega como
`website_context`). Las claves creadas antes de que existiera esta configuración permiten las cuatro,
por lo que las integraciones existentes siguen funcionando; desactiva lo que no uses.

El `context` del widget se trunca a 12,000 caracteres (aproximadamente 3,400
tokens de texto típico en inglés) y cuenta para el
[recargo por tamaño del prompt](/es/guides/billing-and-topups).
Se indica al agente que lo trate como contenido de la página, no como instrucciones.

## Webhooks del modo widget

Cuando `mode="webhook"`, ThunderPhone llama a tu `webhook_url` al inicio de cada
sesión con una carga útil `web.incoming`. Devuelve la configuración del agente
que quieres ejecutar para ese visitante — sigue el mismo
[esquema de respuesta](/es/webhooks/call-incoming) que las
llamadas telefónicas:

```json
{
  "prompt":  "You are a VIP concierge for Jane Doe.",
  "voice":   "john",
  "product": "storm-base",
  "tools":   [ /* per-customer tools */ ]
}
```

Puedes combinar el contexto de tu propia sesión (qué cliente está navegando,
en qué página está) en el prompt e intercambiar agentes en cada lanzamiento gradual.

## Observa las sesiones

Las sesiones del widget aparecen en
[`GET /v1/calls`](/api-reference/calls#list-calls) con
`direction="widget"` — la misma transcripción, grabación, evaluación y
facturación que las llamadas telefónicas. Filtra por `direction` para crear un
panel exclusivo para el widget.

---

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Referencia del SDK del widget" icon="window-maximize" href="/es/widget/overview">
    Detalles de integración con React / hook / CDN.
  </Card>
  <Card title="Configuración dinámica por llamada" icon="bolt" href="/es/guides/dynamic-call-config">
    Implementa el flujo `mode="webhook"` de principio a fin.
  </Card>
  <Card title="Referencia de claves publicables" icon="key" href="/api-reference/publishable-keys">
    Todos los campos del recurso de clave.
  </Card>
  <Card title="API de sesiones de micrófono" icon="microphone" href="/api-reference/mic-sessions">
    Omite el widget; controla LiveKit directamente para interfaces personalizadas.
  </Card>
</CardGroup>
