---
title: "Funktionsverktyg"
description: "Ge dina AI-agenter funktionsverktyg som anropar externa API:er mitt i samtalet — hämta kunddata, boka tider, uppdatera poster — med typade parametrar."
---

Funktionsverktyg gör det möjligt för dina röstagenter att anropa externa API:er under telefonsamtal. Använd dem för att slå upp kunddata, kontrollera tillgänglighet, boka tider eller utföra valfri åtgärd som din backend stöder.

## Så fungerar det

1. Du definierar verktyg med ett schema (vilka argument verktyget accepterar)
2. Du tillhandahåller en `endpoint`-konfiguration (där ThunderPhone anropar ditt API) — eller utelämnar den för att ta emot verktygsanrop på din organisations webhook
3. Under ett samtal avgör AI:n när ett verktyg ska användas baserat på konversationen
4. ThunderPhone anropar din endpoint med verktygsargumenten
5. Ditt API-svar skickas tillbaka till AI:n för att fortsätta konversationen

| Funktion | Var det körs | Konfiguration |
| --- | --- | --- |
| [Inbyggda verktyg](/sv/guides/built-in-tools) | ThunderPhone | Promptinstruktioner; vissa verktyg kräver även en agentinställning |
| [Appanslutningar](/sv/guides/connect-apps) | ThunderPhone och den anslutna leverantören | Anslut kontot och koppla godkända åtgärder |
| [API-anslutningar](/sv/guides/api-connections) och funktionsverktyg | Ditt HTTP API | Definiera endpoint och schema, eller ta emot funktionsanrop via en webhook |
| [MCP-servrar](/sv/guides/mcp-servers) | En fjärransluten MCP-server | Lägg till servern, identifiera dess verktyg och koppla den till agenten |

---

## Verktygsschema

Varje verktyg följer den här strukturen:

```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
}
```

### Verktygskonfiguration

| Fält | Typ | Obligatoriskt | Beskrivning |
|-------|------|----------|-------------|
| `timeout` | number | Nej | Längsta körtid i sekunder (standard: `20`, högsta: `180`) |

### Funktionsdefinition

| Fält | Typ | Obligatoriskt | Beskrivning |
|-------|------|----------|-------------|
| `name` | string | Ja | Unik identifierare för verktyget |
| `description` | string | Ja | Förklarar för AI:n när verktyget ska användas |
| `parameters` | object | Ja | JSON Schema för verktygsargument |

### Endpoint-konfiguration

| Fält | Typ | Obligatoriskt | Beskrivning |
|-------|------|----------|-------------|
| `url` | string | Ja | URL:en för din API-endpoint |
| `method` | string | Nej | HTTP-metod (standard: `POST`) |
| `headers` | object | Nej | Anpassade headers som ska inkluderas |

<Note>
  `endpoint`-konfigurationen skickas **inte** till AI-modellen — den används endast av ThunderPhone för att utföra verktygsanropet.
</Note>

---

## Två anropsvägar

Vilken begäran din server tar emot beror på om verktyget har en
`endpoint`:

| | Verktyg **med** `endpoint` | Verktyg **utan** `endpoint` |
|---|---|---|
| Vart begäran skickas | Direkt till `endpoint.url` | Organisationens [äldre webhook-URL](/api-reference/organizations#legacy-single-url-webhook) |
| Brödtext | Valt begärandeformat (rena argument som standard) | `telephony.tool` / `web.tool`-kuvert |
| Rubriker | Dina `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` |
| Signeringsnyckel | Organisationens webhook-hemlighet | Organisationens webhook-hemlighet |

Båda vägarna är **blockerande** — AI:n väntar mitt i meningen på
resultatet. Standardtidsgränsen är **20 s**; ange verktygets `timeout` på toppnivå
för att tillåta längre körning, upp till plattformens högsta gräns på **180 s**.
Håll hanterare snabba. En blandning fungerar bra:
i ett samtal vars organisation har en webhook-URL anropas verktyg med en `endpoint`
direkt, medan övriga använder webhooken som reserv.

## Direktanrop till ändpunkter

När AI:n anropar ett verktyg som har en `endpoint` skickar ThunderPhone
en begäran till din URL:

### Begärandehuvuden

```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
```

Anpassade HTTP-huvuden från dina `endpoint.headers` inkluderas alltid
ordagrant, tillsammans med dessa ThunderPhone-namnområdesindelade HTTP-huvuden:

- `X-ThunderPhone-Signature-V2` — tidsstämplad HMAC-SHA256 för
  metod, sökväg, frågesträng och begärandetext, med din **organisations webhook-hemlighet** som nyckel.
  Verifiera denna och avvisa begäranden som är äldre än 5 minuter. Se
  [Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — äldre HMAC-SHA256 för de exakta
  byten i begärandetexten, med samma nyckel, som fortfarande skickas för befintliga verifierare
- `X-ThunderPhone-Call-ID` — Det aktuella samtals-ID:t

`Content-Type: application/json` anges om inte dina `endpoint.headers`
åsidosätter det — en anpassad `Content-Type` gäller.

<Warning>
  Signaturen använder webhookhemligheten på organisationsnivå från
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook) som nyckel.
  Om din organisation aldrig har konfigurerat den äldre webhooken finns
  ingen hemlighet och verktygsanrop innehåller **endast**
  `X-ThunderPhone-Call-ID` — en hanterare som misslyckas direkt när en
  signatur saknas skulle avvisa dem.
  Konfigurera antingen den äldre webhooken för att få en hemlighet, eller
  lägg din egen delade hemlighet i `endpoint.headers`.
</Warning>

### Begärandeformat

Varje API-anslutning har en inställning för `request_format`. Välj `vapi` eller
`retell` när ett importerat verktyg ska fortsätta anropa en endpoint som är byggd för
den plattformen. Nya och befintliga anslutningar använder `standard` som standard.

För `POST` / `PUT` / `PATCH` serialiserar ThunderPhone det valda
kuvertet kanoniskt (sorterade nycklar, kompakta avgränsare). Båda signaturerna
omfattar exakt dessa byte.

#### Standard

`standard` skickar endast verktygsargumenten, utan omslutning. Detta är
standardläget och bevarar ThunderPhones ursprungliga begärandetext byte för byte:

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

#### Vapi

`vapi` skickar Vapis [`tool-calls`-servermeddelande](https://docs.vapi.ai/tools/custom-tools)
i det format som Vapi dokumenterar. `call.id` är ThunderPhone-anrops-ID:t. Verktygsanrops-
`id` är unikt för anropet och återanvänds för att matcha endpointens resultat. Anropet
visas i både `toolCallList` och `toolWithToolCallList`, med `function.arguments`
som ett JSON-objekt:

```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" }
          }
        }
      }
    ]
  }
}
```

Vapi-specifika fält som `assistant`, `artifact` och hela `call`-objektet
skickas inte.

Returnera Vapis svarsformat med matchande verktygsanrops-ID:

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

ThunderPhone ger AI:n den uppackade `result`-strängen. Det accepterar även ett
JSON-värde för kompatibilitet med endpoints som redan returnerar ett sådant. Ett
`error` i stället för `result` returneras till AI:n som ett verktygsfel. Om
endpointen returnerar ett felaktigt eller omatchat Vapi-kuvert loggar ThunderPhone
en varning och faller tillbaka till sin standardtolkning av svar i stället för att
misslyckas med anropet.

#### Retell

`retell` skickar begäran som dokumenteras för en [anpassad Retell-
funktion](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" }
}
```

`call`-objektet innehåller endast `call_id`. ThunderPhone inkluderar inte
Retell-specifika fält som det inte kan ange korrekt vid utskick, till exempel
`agent_id`, `transcript`, `transcript_object`, `metadata` eller dynamiska
variabler. Retell accepterar valfri svarskropp; ThunderPhone skickar den vidare
med standardhanteringen av svar.

För `GET` / `DELETE` skickas argumenten som **frågeparametrar**
och brödtexten är tom oavsett `request_format`. V2-signaturen
omfattar frågesträngen som den skickas; den äldre signaturen beräknas över
den tomma bytesträngen. Se
[Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures).

### Svar

Returnera ett JSON-svar med verktygsresultatet:

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

Svaret formateras och skickas till AI:n för att fortsätta
konversationen. Icke-JSON-svar omsluts som `{"data": "<text>"}`;
tidsgränser och anslutningsfel rapporteras till AI:n som fel, så att
röstagenten kan be om ursäkt och gå vidare i stället för att fastna.

## Dirigering i webhook-läge

Verktyg **utan** en `endpoint` skickas till din organisations äldre
webhook-URL som en signerad begäran av typen `telephony.tool`
(telefonsamtal) eller `web.tool` (webbsamtal). Till skillnad från
[revisionsnotiserna](/sv/webhooks/events) som levereras till webhookändpunkter
efter körning **är** denna begäran körningen — ditt HTTP-svar är
verktygsresultatet.

```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` innehåller `origin_domain` i stället för `from_number` /
`to_number`. Svara med verktygsresultatet som JSON — samma svarskontrakt
som för direkta endpoint-anrop. Begäran signeras med organisationens
webhook-hemlighet, med båda signaturhuvudena, precis som alla andra webhooks.

<Note>
  Prenumererande [webhookändpunkter](/sv/webhooks/endpoints) får dessutom
  en icke-blockerande `telephony.tool` / `web.tool`-**notis efter**
  varje verktygskörning (oavsett vilken väg som körde det), inklusive
  verktygets svar — användbart för revisionsspår. Se
  [händelsekatalogen](/sv/webhooks/events).
</Note>

---

## Signaturverifiering

Direkta verktygsanrop signeras på samma sätt som webhooks, med din
organisations webhook-hemlighet som nyckel:

- `X-ThunderPhone-Signature-V2` signerar tidsstämpeln, metoden, sökvägen och
  frågesträngen som de skickas, samt de exakta byte i brödtexten. Avvisa begäranden vars
  tidsstämpel skiljer sig mer än 5 minuter från din klocka.
- `X-ThunderPhone-Signature` (äldre) signerar endast byte i brödtexten, den
  tomma bytesträngen för `GET` / `DELETE`. Den skickas fortfarande, oförändrad.

SDK-hjälpfunktionerna kontrollerar V2-huvudet, inklusive 5-minutersfönstret:

<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>

Fullständiga recept — den signerade strängen, ett genomarbetat exempel, kod utan
SDK, fallet `GET` / `DELETE` och förbehållet om ingen hemlighet — finns i [Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures).

---

## Exempel: Komplett bokningsflöde

Här är en uppsättning verktyg för ett komplett system för tidsbokning:

```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" }
      }
    }
  ]
}
```

---

## Bästa praxis

<AccordionGroup>
  <Accordion title="Skriv tydliga beskrivningar">
    Fältet `description` hjälper AI:n att förstå **när** verktyget ska användas. Var specifik med vad det gör och när det är lämpligt.
  </Accordion>

  <Accordion title="Hantera fel smidigt">
    Returnera felmeddelanden som AI:n kan förstå: `{"error": "No slots available for that date"}` i stället för generiska 500-fel.
  </Accordion>

  <Accordion title="Håll svaren kortfattade">
    Returnera endast det AI:n behöver för att fortsätta samtalet. Stora nyttolaster fördröjer svarstiderna.
  </Accordion>

  <Accordion title="Använd obligatoriska fält med omsorg">
    Markera fält som `required` endast när det verkligen behövs. AI:n ber användaren om obligatorisk information innan verktyget anropas.
  </Accordion>
</AccordionGroup>

---

## Relaterat

<CardGroup cols={2}>
  <Card title="Inbyggda verktyg" icon="wrench" href="/sv/guides/built-in-tools">
    Initiera plattformshanterade samtalsåtgärder utan att definiera en slutpunkt.
  </Card>
  <Card title="Appanslutningar" icon="plug" href="/sv/guides/connect-apps">
    Plattformshanterade verktyg för HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets och Cal.com — ingen slutpunkt krävs.
  </Card>
  <Card title="MCP-servrar" icon="server" href="/sv/guides/mcp-servers">
    Anslut en MCP-server och låt agenten anropa dess verktyg.
  </Card>
  <Card title="API-anslutningar" icon="code" href="/sv/guides/api-connections">
    Återanvändbara REST-integrationer som du kan ansluta till agenter.
  </Card>
  <Card title="Verifiera webhook-signaturer" icon="shield-check" href="/sv/guides/verify-webhook-signatures">
    En verifieringshjälp för webhooks och verktygsanrop.
  </Card>
</CardGroup>
