---
title: "Funksjonsverktøy"
description: "Gi AI-agentene dine funksjonsverktøy som kaller eksterne API-er midt i samtalen – hent kundedata, bestill avtaler, oppdater oppføringer – med typede parametere."
---

Funksjonsverktøy lar stemmeagentene dine kalle eksterne API-er under telefonsamtaler. Bruk dem til å slå opp kundedata, sjekke tilgjengelighet, bestille avtaler eller utføre enhver handling backend-en din støtter.

## Slik fungerer det

1. Du definerer verktøy med et skjema (hvilke argumenter verktøyet godtar)
2. Du oppgir en `endpoint`-konfigurasjon (der ThunderPhone kaller API-et ditt) — eller utelater den for å motta verktøykall på organisasjonens webhook
3. Under en samtale avgjør AI-en når den skal bruke et verktøy basert på samtalen
4. ThunderPhone kaller endepunktet ditt med verktøyargumentene
5. API-svaret ditt mates tilbake til AI-en for å fortsette samtalen

| Mulighet | Hvor det kjører | Oppsett |
| --- | --- | --- |
| [Innebygde verktøy](/nb/guides/built-in-tools) | ThunderPhone | Promptinstruksjoner; enkelte verktøy trenger også en agentinnstilling |
| [Appkoblinger](/nb/guides/connect-apps) | ThunderPhone og den tilkoblede leverandøren | Koble til kontoen og legg til godkjente handlinger |
| [API-koblinger](/nb/guides/api-connections) og funksjonsverktøy | HTTP API-et ditt | Definer endepunktet og skjemaet, eller motta funksjonskall via en webhook |
| [MCP-servere](/nb/guides/mcp-servers) | En ekstern MCP-server | Legg til serveren, oppdag verktøyene og legg den til agenten |

---

## Verktøyskjema

Hvert verktøy følger denne 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
}
```

### Verktøykonfigurasjon

| Felt | Type | Påkrevd | Beskrivelse |
|-------|------|----------|-------------|
| `timeout` | tall | Nei | Maksimal kjøretid i sekunder (standard: `20`, maksimum: `180`) |

### Funksjonsdefinisjon

| Felt | Type | Påkrevd | Beskrivelse |
|-------|------|----------|-------------|
| `name` | streng | Ja | Unik identifikator for verktøyet |
| `description` | streng | Ja | Forklarer AI-en når dette verktøyet skal brukes |
| `parameters` | objekt | Ja | JSON Schema for verktøyargumenter |

### Endepunktkonfigurasjon

| Felt | Type | Påkrevd | Beskrivelse |
|-------|------|----------|-------------|
| `url` | streng | Ja | URL-en til API-endepunktet ditt |
| `method` | streng | Nei | HTTP-metode (standard: `POST`) |
| `headers` | objekt | Nei | Egendefinerte headere som skal inkluderes |

<Note>
  `endpoint`-konfigurasjonen sendes **ikke** til AI-modellen—den brukes bare av ThunderPhone til å utføre verktøykallet.
</Note>

---

## To kallbaner

Hvilken forespørsel serveren din mottar, avhenger av om verktøyet har et
`endpoint`:

| | Verktøy **med** `endpoint` | Verktøy **uten** `endpoint` |
|---|---|---|
| Hvor forespørselen sendes | Direkte til `endpoint.url` | Organisasjonens [eldre webhook-URL](/api-reference/organizations#legacy-single-url-webhook) |
| Brødtekst | Valgt forespørselsformat (rene argumenter som standard) | `telephony.tool` / `web.tool`-omslag |
| Headere | Dine `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` |
| Signeringsnøkkel | Organisasjonens webhook-hemmelighet | Organisasjonens webhook-hemmelighet |

Begge banene er **blokkerende** — AI-en venter midt i en setning på
resultatet. Standard tidsavbrudd er **20 s**; angi verktøyets øverste
`timeout` for å tillate lengre kjøretid, opptil plattformens maksimum på
**180 s**. Hold behandlere raske. En blanding fungerer fint:
I en samtale der organisasjonen har en webhook-URL, kalles verktøy med et `endpoint`
direkte, mens resten faller tilbake til webhooken.

## Direkte endepunktkall

Når stemmeagenten kaller et verktøy som har et `endpoint`, sender ThunderPhone
en forespørsel til URL-en din:

### Forespørselsoverskrifter

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

Egendefinerte headere fra `endpoint.headers` inkluderes alltid
ordrett, i tillegg til disse ThunderPhone-navngitte headerne:

- `X-ThunderPhone-Signature-V2` — tidsstemplet HMAC-SHA256 over
  metode, sti, spørring og brødtekst, med din **organisasjons webhook-hemmelighet** som nøkkel.
  Verifiser denne og avvis forespørsler som er eldre enn 5 minutter. Se
  [Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — eldre HMAC-SHA256 av de nøyaktige
  bytene i forespørselsbrødteksten, med samme nøkkel, fortsatt sendt for eksisterende verifikatorer
- `X-ThunderPhone-Call-ID` — ID-en for gjeldende samtale

`Content-Type: application/json` angis med mindre `endpoint.headers`
overstyrer den — en egendefinert `Content-Type` har forrang.

<Warning>
  Signaturen bruker webhook-hemmeligheten på organisasjonsnivå fra
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook).
  Hvis organisasjonen din aldri har konfigurert den eldre webhooken, finnes det
  ingen hemmelighet, og verktøykall inneholder **bare** `X-ThunderPhone-Call-ID` —
  en behandler som avviser forespørsler uten signatur, vil avvise dem.
  Konfigurer enten den eldre webhooken for å få en hemmelighet, eller legg din egen
  delte hemmelighet i `endpoint.headers`.
</Warning>

### Forespørselsformat

Hver API-tilkobling har en `request_format`-innstilling. Velg `vapi` eller
`retell` når et importert verktøy skal fortsette å kalle et endepunkt som er bygget for
den plattformen. Nye og eksisterende tilkoblinger bruker `standard` som standard.

For `POST` / `PUT` / `PATCH` serialiserer ThunderPhone den valgte
konvolutten kanonisk (sorterte nøkler, kompakte skilletegn). Begge signaturene
dekker nøyaktig disse bytene.

#### Standard

`standard` sender bare verktøyargumentene, uten innpakning. Dette er
standardinnstillingen og bevarer ThunderPhone sin opprinnelige forespørselskropp byte for byte:

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

#### Vapi

`vapi` sender Vapis [`tool-calls`-servermelding](https://docs.vapi.ai/tools/custom-tools)
i formen Vapi dokumenterer. `call.id` er ThunderPhone-anrops-ID-en. Verktøykallets
`id` er unik for påkallingen og brukes på nytt til å matche
endepunktets resultat. Kallet vises i både `toolCallList` og
`toolWithToolCallList`, med `function.arguments` som et 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-spesifikke felt som `assistant`, `artifact` og det fullstendige `call`-objektet
sendes ikke.

Returner Vapis svarkonvolutt med samsvarende verktøykall-ID:

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

ThunderPhone gir AI-en den utpakkede `result`-strengen. Den godtar også en
JSON-verdi for kompatibilitet med endepunkter som allerede returnerer en. En
`error` i stedet for `result` returneres til AI-en som en verktøyfeil. Hvis
endepunktet returnerer en feilformatert eller ikke-samsvarende Vapi-konvolutt, logger ThunderPhone
en advarsel og faller tilbake til standard svarbehandling i stedet for å avbryte
anropet.

#### Retell

`retell` sender forespørselen som er dokumentert for en [egendefinert Retell-
funksjon](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 inkluderer bare `call_id`. ThunderPhone inkluderer ikke
Retell-spesifikke felt den ikke kan oppgi korrekt ved utsending, for eksempel
`agent_id`, `transcript`, `transcript_object`, `metadata` eller dynamiske
variabler. Retell godtar enhver svartekst; ThunderPhone sender den videre
med standard svarbehandling.

For `GET` / `DELETE` sendes argumentene som **spørringsparametere**
og brødteksten er tom uavhengig av `request_format`. V2-signaturen
dekker spørringsstrengen slik den sendes; den eldre signaturen beregnes over
den tomme bytestrengen. Se
[Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures).

### Svar

Returner et JSON-svar med verktøyresultatet:

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

Svaret formateres og gis til stemmeagenten for å fortsette
samtalen. Svar som ikke er JSON, pakkes inn som `{"data": "<text>"}`;
tidsavbrudd og tilkoblingsfeil rapporteres til stemmeagenten som feil, slik
at agenten kan beklage og gå videre i stedet for å stoppe opp.

## Ruting i webhook-modus

Verktøy **uten** et `endpoint` rutes til organisasjonens eldre
webhook-URL som en signert `telephony.tool`-forespørsel (telefonanrop) eller
`web.tool`-forespørsel (nettanrop). I motsetning til [revisjonsvarslene](/nb/webhooks/events)
som leveres til webhook-endepunkter etter utførelse, **er** denne forespørselen
selve utførelsen — HTTP-svaret ditt er verktøyresultatet.

```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` inneholder `origin_domain` i stedet for `from_number` /
`to_number`. Svar med verktøyresultatet som JSON — samme svarkontrakt
som ved direkte endepunktkall. Forespørselen er signert med organisasjonens
webhook-hemmelighet, med begge signaturheaderne, som alle andre webhooks.

<Note>
  Abonnerte [webhook-endepunkter](/nb/webhooks/endpoints) mottar i tillegg
  et ikke-blokkerende `telephony.tool`- / `web.tool`-**varsel etter**
  at hvert verktøy er kjørt (uansett hvilken bane som kjørte det), inkludert
  verktøyets svar — nyttig for revisjonsspor. Se
  [hendelseskatalogen](/nb/webhooks/events).
</Note>

---

## Signaturverifisering

Direkte verktøykall signeres på samme måte som webhooks, med organisasjonens
webhook-hemmelighet som nøkkel:

- `X-ThunderPhone-Signature-V2` signerer tidsstempelet, metoden, banen og
  spørringen slik de sendes, samt de nøyaktige byteverdiene i brødteksten. Avvis forespørsler der
  tidsstempelet avviker med mer enn 5 minutter fra klokken din.
- `X-ThunderPhone-Signature` (eldre) signerer bare byteverdiene i brødteksten, den
  tomme bytestrengen for `GET` / `DELETE`. Den sendes fortsatt, uendret.

SDK-hjelpefunksjonene kontrollerer V2-headeren, inkludert 5-minuttersvinduet:

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

Fullstendige oppskrifter — den signerte strengen, et gjennomarbeidet eksempel, kode uten
SDK-et, tilfellet `GET` / `DELETE` og forbeholdet om ingen hemmelighet — finner du i [Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures).

---

## Eksempel: Fullstendig bestillingsflyt

Her er et sett med verktøy for et komplett system for timebestilling:

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

---

## Beste praksis

<AccordionGroup>
  <Accordion title="Skriv tydelige beskrivelser">
    Feltet `description` hjelper AI-en med å forstå **når** verktøyet skal brukes. Vær spesifikk om hva det gjør, og når det passer å bruke det.
  </Accordion>

  <Accordion title="Håndter feil på en god måte">
    Returner feilmeldinger AI-en kan forstå: `{"error": "No slots available for that date"}` i stedet for generiske 500-feil.
  </Accordion>

  <Accordion title="Hold svarene korte">
    Returner bare det AI-en trenger for å fortsette samtalen. Store nyttelaster gir tregere responstider.
  </Accordion>

  <Accordion title="Bruk obligatoriske felt med omtanke">
    Merk felt som `required` bare når det er helt nødvendig. AI-en vil be brukeren om obligatorisk informasjon før den kaller verktøyet.
  </Accordion>
</AccordionGroup>

---

## Relatert

<CardGroup cols={2}>
  <Card title="Innebygde verktøy" icon="wrench" href="/nb/guides/built-in-tools">
    Be plattformadministrerte samtalehandlinger uten å definere et endepunkt.
  </Card>
  <Card title="App-tilkoblinger" icon="plug" href="/nb/guides/connect-apps">
    Plattformadministrerte verktøy for HubSpot, Salesforce, Slack, Google
    Kalender, Google Regneark og Cal.com — ingen endepunkt kreves.
  </Card>
  <Card title="MCP-servere" icon="server" href="/nb/guides/mcp-servers">
    Koble til en MCP-server og la agenten kalle verktøyene dens.
  </Card>
  <Card title="API-tilkoblinger" icon="code" href="/nb/guides/api-connections">
    Gjenbrukbare REST-integrasjoner du kan koble til agenter.
  </Card>
  <Card title="Verifiser webhook-signaturer" icon="shield-check" href="/nb/guides/verify-webhook-signatures">
    Én verifiseringshjelper for webhooks og verktøykall.
  </Card>
</CardGroup>
