---
title: "Herramientas de función"
description: "Proporciona a tus agentes de IA herramientas de función que llaman a API externas durante la conversación: obtén datos de clientes, agenda citas, actualiza registros, con parámetros tipados."
---

Las herramientas de función permiten que tus agentes de IA invoquen API externas durante las llamadas telefónicas. Úsalas para consultar datos de clientes, verificar disponibilidad, reservar citas o realizar cualquier acción que admita tu backend.

## Cómo funciona

1. Defines herramientas con un esquema (qué argumentos acepta la herramienta)
2. Proporcionas una configuración de `endpoint` (dónde ThunderPhone llama a tu API) o la omites para recibir llamadas de herramientas en el webhook de tu organización
3. Durante una llamada, la IA decide cuándo usar una herramienta según la conversación
4. ThunderPhone llama a tu endpoint con los argumentos de la herramienta
5. La respuesta de tu API se devuelve a la IA para continuar la conversación

| Capacidad | Dónde se ejecuta | Configuración |
| --- | --- | --- |
| [Herramientas integradas](/es/guides/built-in-tools) | ThunderPhone | Instrucciones del prompt; algunas herramientas también necesitan una configuración del agente |
| [Conexiones de aplicaciones](/es/guides/connect-apps) | ThunderPhone y el proveedor conectado | Conecta la cuenta y adjunta acciones aprobadas |
| [Conexiones de API](/es/guides/api-connections) y herramientas de función | Tu API HTTP | Define el endpoint y el esquema, o recibe llamadas de función mediante un webhook |
| [Servidores MCP](/es/guides/mcp-servers) | Un servidor MCP remoto | Agrega el servidor, descubre sus herramientas y adjúntalo al agente |

---

## Esquema de la herramienta

Cada herramienta sigue esta estructura:

```json
{
  "type": "function",
  "function": {
    "name": "search_appointments",
    "description": "Find available appointment slots for a given date",
    "parameters": {
      "type": "object",
      "properties": {
        "date": {
          "type": "string",
          "description": "Date in YYYY-MM-DD format"
        },
        "service": {
          "type": "string",
          "description": "Type of service (e.g., 'consultation', 'follow-up')"
        }
      },
      "required": ["date"]
    }
  },
  "endpoint": {
    "url": "https://api.example.com/appointments/search",
    "method": "POST",
    "headers": {
      "X-Api-Key": "your-api-key"
    }
  },
  "timeout": 120
}
```

### Configuración de la herramienta

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|----------|-------------|
| `timeout` | número | No | Tiempo máximo de ejecución en segundos (predeterminado: `20`, máximo: `180`) |

### Definición de función

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|----------|-------------|
| `name` | cadena | Sí | Identificador único de la herramienta |
| `description` | cadena | Sí | Explica a la IA cuándo usar esta herramienta |
| `parameters` | objeto | Sí | Esquema JSON para los argumentos de la herramienta |

### Configuración del endpoint

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|----------|-------------|
| `url` | cadena | Sí | URL del endpoint de tu API |
| `method` | cadena | No | Método HTTP (predeterminado: `POST`) |
| `headers` | objeto | No | Encabezados personalizados que se incluirán |

<Note>
  La configuración de `endpoint` **no** se envía al modelo de IA; ThunderPhone solo la usa para ejecutar la llamada de la herramienta.
</Note>

---

## Dos rutas de invocación

La solicitud que recibe tu servidor depende de si la herramienta tiene un
`endpoint`:

| | Herramienta **con** `endpoint` | Herramienta **sin** `endpoint` |
|---|---|---|
| Destino de la solicitud | Directamente a `endpoint.url` | La [URL de webhook heredada](/api-reference/organizations#legacy-single-url-webhook) de tu organización |
| Cuerpo | Formato de solicitud seleccionado (argumentos sin envoltorio de forma predeterminada) | Envoltorio `telephony.tool` / `web.tool` |
| Encabezados | Tus `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` |
| Clave de firma | Secreto de webhook de la organización | Secreto de webhook de la organización |

Ambas rutas son **bloqueantes**: la IA espera el resultado a mitad de la
oración. El tiempo de espera predeterminado es de **20 s**; configura el
`timeout` de nivel superior de la herramienta para permitir una ejecución más larga,
hasta el máximo de la plataforma de **180 s**. Mantén los controladores rápidos. Una combinación está bien:
en una llamada cuya organización tiene una URL de webhook, las herramientas con un `endpoint` se
llaman directamente y las demás recurren al webhook.

## Llamadas directas a endpoints

Cuando la IA invoca una herramienta que tiene un `endpoint`, ThunderPhone envía
una solicitud a tu URL:

### Encabezados de la solicitud

```http
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature-V2: t=1767225600,v1=5f2c...
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

Los encabezados personalizados de tu `endpoint.headers` siempre se incluyen
literalmente, además de estos encabezados con espacio de nombres de ThunderPhone:

- `X-ThunderPhone-Signature-V2` — HMAC-SHA256 con marca de tiempo sobre el
  método, la ruta, la consulta y el cuerpo, firmado con tu **secreto de webhook de la organización**.
  Verifica este y rechaza las solicitudes con más de 5 minutos de antigüedad; consulta
  [Verificar firmas de webhook](/es/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — HMAC-SHA256 heredado de los bytes exactos
  del cuerpo de la solicitud, con la misma clave, que aún se envía para los verificadores existentes
- `X-ThunderPhone-Call-ID` — El ID de la llamada actual

Se establece `Content-Type: application/json`, a menos que tu `endpoint.headers`
lo reemplace; un `Content-Type` personalizado tiene prioridad.

<Warning>
  La firma usa como clave el secreto de webhook a nivel de organización de
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook).
  Si tu organización nunca configuró el webhook heredado, no hay
  secreto y las llamadas a herramientas incluyen **solo** `X-ThunderPhone-Call-ID`;
  un controlador que falla de forma estricta ante una firma faltante las rechazaría.
  Configura el webhook heredado para obtener un secreto o incluye tu propio
  secreto compartido en `endpoint.headers`.
</Warning>

### Formato de solicitud

Cada conexión de API tiene una configuración `request_format`. Elige `vapi` o
`retell` cuando una herramienta importada deba seguir llamando a un endpoint creado para
esa plataforma. Las conexiones nuevas y existentes usan `standard` de forma predeterminada.

Para `POST` / `PUT` / `PATCH`, ThunderPhone serializa el
contenedor seleccionado de forma canónica (claves ordenadas, separadores compactos). Ambas firmas
cubren estos bytes exactos.

#### Estándar

`standard` envía solo los argumentos de la herramienta, sin envoltorio. Este es el valor
predeterminado y conserva el cuerpo de solicitud original de ThunderPhone byte por byte:

```json
{"date":"2025-01-02","service":"consultation"}
```

#### Vapi

`vapi` envía el [mensaje de servidor `tool-calls` de Vapi](https://docs.vapi.ai/tools/custom-tools)
con la estructura documentada por Vapi. El `call.id` es el ID de llamada de ThunderPhone. El
`id` de la llamada de herramienta es único para la invocación y se reutiliza para coincidir con el
resultado del endpoint. La llamada aparece tanto en `toolCallList` como en
`toolWithToolCallList`, con `function.arguments` como un objeto JSON:

```json
{
  "message": {
    "type": "tool-calls",
    "timestamp": 1725000000123,
    "call": { "id": "987654321" },
    "toolCallList": [
      {
        "id": "call_00112233445566778899aabbccddeeff",
        "type": "function",
        "function": {
          "name": "search_appointments",
          "arguments": { "date": "2025-01-02", "service": "consultation" }
        }
      }
    ],
    "toolWithToolCallList": [
      {
        "type": "function",
        "function": { "name": "search_appointments" },
        "toolCall": {
          "id": "call_00112233445566778899aabbccddeeff",
          "type": "function",
          "function": {
            "name": "search_appointments",
            "arguments": { "date": "2025-01-02", "service": "consultation" }
          }
        }
      }
    ]
  }
}
```

No se envían campos exclusivos de Vapi como `assistant`, `artifact` y el objeto `call`
completo.

Devuelve el contenedor de respuesta de Vapi con el ID de llamada de herramienta correspondiente:

```json
{
  "results": [
    {
      "toolCallId": "call_00112233445566778899aabbccddeeff",
      "result": "{\"available_slots\":[\"9:00 AM\",\"2:00 PM\"]}"
    }
  ]
}
```

ThunderPhone proporciona a la IA la cadena `result` sin el contenedor. También acepta un
valor JSON para ofrecer compatibilidad con endpoints que ya devuelven uno. Un
`error` en lugar de `result` se devuelve a la IA como un error de herramienta. Si el
endpoint devuelve un contenedor de Vapi malformado o no coincidente, ThunderPhone registra
una advertencia y recurre a su análisis de respuesta estándar en lugar de provocar un error en
la llamada.

#### Retell

`retell` envía la solicitud documentada para una [función personalizada de
Retell](https://docs.retellai.com/build/single-multi-prompt/custom-function):

```json
{
  "name": "search_appointments",
  "call": { "call_id": "987654321" },
  "args": { "date": "2025-01-02", "service": "consultation" }
}
```

El objeto `call` incluye solo `call_id`. ThunderPhone no incluye
campos específicos de Retell que no puede proporcionar de forma precisa al realizar el envío, como
`agent_id`, `transcript`, `transcript_object`, `metadata` o variables
dinámicas. Retell acepta cualquier cuerpo de respuesta; ThunderPhone lo transmite
mediante el manejo de respuesta estándar.

Para `GET` / `DELETE`, los argumentos se envían como **parámetros de consulta**
y el cuerpo está vacío independientemente de `request_format`. La firma V2
cubre la cadena de consulta tal como se envía; la firma heredada se calcula sobre
la cadena de bytes vacía. Consulta
[Verificar firmas de webhooks](/es/guides/verify-webhook-signatures).

### Respuesta

Devuelve una respuesta JSON con el resultado de la herramienta:

```json
{
  "available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
  "timezone": "America/Los_Angeles"
}
```

La respuesta se formatea y se proporciona a la IA para continuar la
conversación. Las respuestas que no son JSON se envuelven como `{"data": "<text>"}`;
los tiempos de espera y fallas de conexión se reportan a la IA como errores, para que
el agente pueda disculparse y continuar en lugar de quedarse bloqueado.

## Envío en modo webhook

Las herramientas **sin** un `endpoint` se envían a la URL de webhook heredada de tu
organización como una solicitud firmada `telephony.tool` (llamadas telefónicas) o `web.tool`
(llamadas web). A diferencia de las [notificaciones de auditoría](/es/webhooks/events)
entregadas a endpoints de webhook después de la ejecución, esta solicitud **es**
la ejecución; tu respuesta HTTP es el resultado de la herramienta.

```json
{
  "type": "telephony.tool",
  "data": {
    "call_id": 987654321,
    "tool_name": "search_appointments",
    "arguments": { "date": "2026-04-21" },
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  }
}
```

`web.tool` incluye `origin_domain` en lugar de `from_number` /
`to_number`. Responde con el resultado de la herramienta como JSON: el mismo contrato de respuesta que las llamadas directas al endpoint. La solicitud se firma con el secreto de webhook de la organización, con ambos encabezados de firma, como cualquier otro webhook.

<Note>
  Los [endpoints de webhook](/es/webhooks/endpoints) suscritos también
  reciben una **notificación** `telephony.tool` / `web.tool` que no bloquea
  después de que se ejecuta cada herramienta (sin importar qué ruta la ejecutó), e incluye la
  respuesta de la herramienta; es útil para registros de auditoría. Consulta el
  [catálogo de eventos](/es/webhooks/events).
</Note>

---

## Verificación de firma

Las llamadas directas a herramientas se firman de la misma manera que los webhooks, con la clave secreta de webhooks de tu organización:

- `X-ThunderPhone-Signature-V2` firma la marca de tiempo, el método, la ruta y la
  consulta tal como se enviaron, así como los bytes exactos del cuerpo. Rechaza las solicitudes cuya
  marca de tiempo difiera más de 5 minutos de tu reloj.
- `X-ThunderPhone-Signature` (heredado) firma únicamente los bytes del cuerpo, la
  cadena de bytes vacía para `GET` / `DELETE`. Aún se envía, sin cambios.

Los asistentes del SDK verifican el encabezado V2, incluida la ventana de 5 minutos:

<CodeGroup>
```python Python
import json

from fastapi import FastAPI, HTTPException, Request
from thunderphone import verify_signature

app = FastAPI()

@app.post("/appointments/search")
async def search_appointments(request: Request):
    body = await request.body()
    query = request.scope.get("query_string", b"").decode("latin-1")
    path = request.url.path + (f"?{query}" if query else "")

    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=path,
        body=body,
    ):
        raise HTTPException(status_code=401)

    data = json.loads(body)
    date = data["date"]

    # Look up availability
    slots = await get_available_slots(date)

    return {"available_slots": slots}
```

```javascript Node.js
import express from "express";
import { verifySignature } from "@thunderphone/sdk";

app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
  const ok = verifySignature({
    header: req.header('X-ThunderPhone-Signature-V2'),
    secret: WEBHOOK_SECRET,
    method: req.method,
    path: req.originalUrl,
    body: req.body,
  });
  if (!ok) return res.status(401).send('Invalid signature');

  const { date, service } = JSON.parse(req.body);

  // Look up availability
  const slots = getAvailableSlots(date, service);

  res.json({ available_slots: slots });
});
```
</CodeGroup>

Las recetas completas —la cadena firmada, un ejemplo desarrollado, código sin el
SDK, el caso `GET` / `DELETE` y la advertencia sobre no tener secreto— están en [Verificar firmas de webhooks](/es/guides/verify-webhook-signatures).

---

## Ejemplo: flujo completo de reservas

Aquí tienes un conjunto de herramientas para un sistema completo de reserva de citas:

```json
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "book_appointment",
        "description": "Book an appointment at a specific time",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "time": { "type": "string", "description": "HH:MM format" },
            "customer_name": { "type": "string" },
            "customer_phone": { "type": "string" }
          },
          "required": ["date", "time", "customer_name"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/book",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "cancel_appointment",
        "description": "Cancel an existing appointment",
        "parameters": {
          "type": "object",
          "properties": {
            "confirmation_number": { "type": "string" }
          },
          "required": ["confirmation_number"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/cancel",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    }
  ]
}
```

---

## Buenas prácticas

<AccordionGroup>
  <Accordion title="Escribe descripciones claras">
    El campo `description` ayuda a la IA a entender **cuándo** usar la herramienta. Sé específico sobre lo que hace y cuándo es apropiado usarla.
  </Accordion>

  <Accordion title="Maneja los errores correctamente">
    Devuelve mensajes de error que la IA pueda entender: `{"error": "No slots available for that date"}` en lugar de errores 500 genéricos.
  </Accordion>

  <Accordion title="Mantén las respuestas concisas">
    Devuelve solo lo que la IA necesita para continuar la conversación. Las cargas útiles grandes ralentizan los tiempos de respuesta.
  </Accordion>

  <Accordion title="Usa los campos obligatorios con criterio">
    Marca los campos como `required` solo cuando sea realmente necesario. La IA le pedirá al usuario la información obligatoria antes de llamar a la herramienta.
  </Accordion>
</AccordionGroup>

---

## Relacionado

<CardGroup cols={2}>
  <Card title="Herramientas integradas" icon="wrench" href="/es/guides/built-in-tools">
    Genera acciones de llamadas gestionadas por la plataforma sin definir un endpoint.
  </Card>
  <Card title="Conexiones de aplicaciones" icon="plug" href="/es/guides/connect-apps">
    Herramientas gestionadas por la plataforma para HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets y Cal.com — sin necesidad de un endpoint.
  </Card>
  <Card title="Servidores MCP" icon="server" href="/es/guides/mcp-servers">
    Conecta un servidor MCP y permite que el agente llame a sus herramientas.
  </Card>
  <Card title="Conexiones de API" icon="code" href="/es/guides/api-connections">
    Integraciones REST reutilizables que puedes conectar a los agentes.
  </Card>
  <Card title="Verifica las firmas de webhooks" icon="shield-check" href="/es/guides/verify-webhook-signatures">
    Un único asistente de verificación para webhooks y llamadas de herramientas.
  </Card>
</CardGroup>
