---
title: "Funktionsværktøjer"
description: "Giv dine AI-agenter funktionsværktøjer, der kalder eksterne API'er midt i samtalen — henter kundedata, booker aftaler, opdaterer poster — med typede parametre."
---

Funktionsværktøjer giver dine AI-agenter mulighed for at kalde eksterne API'er under opkald. Brug dem til at slå kundedata op, kontrollere tilgængelighed, booke aftaler eller udføre enhver handling, som din backend understøtter.

## Sådan fungerer det

1. Du definerer værktøjer med et skema (hvilke argumenter værktøjet accepterer)
2. Du angiver en `endpoint`-konfiguration (hvor ThunderPhone kalder dit API) — eller udelader den for at modtage værktøjskald på din organisations webhook
3. Under et opkald beslutter AI'en, hvornår et værktøj skal bruges, baseret på samtalen
4. ThunderPhone kalder dit endpoint med værktøjsargumenterne
5. Dit API-svar sendes tilbage til AI'en for at fortsætte samtalen

| Funktionalitet | Hvor den kører | Opsætning |
| --- | --- | --- |
| [Indbyggede værktøjer](/da/guides/built-in-tools) | ThunderPhone | Promptinstruktioner; nogle værktøjer kræver også en agentindstilling |
| [Appforbindelser](/da/guides/connect-apps) | ThunderPhone og den tilsluttede udbyder | Forbind kontoen, og tilknyt godkendte handlinger |
| [API-forbindelser](/da/guides/api-connections) og funktionsværktøjer | Dit HTTP API | Definer endpointet og skemaet, eller modtag funktionskald via en webhook |
| [MCP-servere](/da/guides/mcp-servers) | En fjern MCP-server | Tilføj serveren, find dens værktøjer, og tilknyt den til agenten |

---

## Værktøjsskema

Hvert værktøj følger denne struktur:

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

### Værktøjskonfiguration

| Felt | Type | Påkrævet | Beskrivelse |
|-------|------|----------|-------------|
| `timeout` | tal | Nej | Maksimal udførelsestid i sekunder (standard: `20`, maksimum: `180`) |

### Funktionsdefinition

| Felt | Type | Påkrævet | Beskrivelse |
|-------|------|----------|-------------|
| `name` | streng | Ja | Unik identifikator for værktøjet |
| `description` | streng | Ja | Forklarer AI'en, hvornår dette værktøj skal bruges |
| `parameters` | objekt | Ja | JSON-skema for værktøjsargumenter |

### Endpointkonfiguration

| Felt | Type | Påkrævet | Beskrivelse |
|-------|------|----------|-------------|
| `url` | streng | Ja | URL'en til dit API-endpoint |
| `method` | streng | Nej | HTTP-metode (standard: `POST`) |
| `headers` | objekt | Nej | Tilpassede headere, der skal inkluderes |

<Note>
  `endpoint`-konfigurationen sendes **ikke** til AI-modellen — den bruges kun af ThunderPhone til at udføre værktøjskaldet.
</Note>

---

## To kaldestier

Hvilken anmodning din server modtager, afhænger af, om værktøjet har et
`endpoint`:

| | Værktøj **med** `endpoint` | Værktøj **uden** `endpoint` |
|---|---|---|
| Hvor anmodningen sendes hen | Direkte til `endpoint.url` | Din organisations [ældre webhook-URL](/api-reference/organizations#legacy-single-url-webhook) |
| Brødtekst | Valgt anmodningsformat (rene argumenter som standard) | `telephony.tool` / `web.tool`-indpakning |
| 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øgle | Organisationens webhook-hemmelighed | Organisationens webhook-hemmelighed |

Begge stier er **blokerende** — AI'en venter midt i en sætning på
resultatet. Standardtimeouttet er **20 s**; angiv værktøjets `timeout`
på øverste niveau for at tillade en længere udførelse, op til platformens
maksimum på **180 s**. Hold handlere hurtige. En blanding er helt fint:
Ved et opkald, hvis organisation har en webhook-URL, kaldes værktøjer med et
`endpoint` direkte, og resten falder tilbage til webhooken.

## Direkte endpointkald

Når AI'en kalder et værktøj, der har et `endpoint`, sender ThunderPhone
en anmodning til din URL:

### Anmodningsheadere

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

Brugerdefinerede headere fra dine `endpoint.headers` medtages altid
uændret sammen med disse ThunderPhone-navnerumsheadere:

- `X-ThunderPhone-Signature-V2` — tidsstemplet HMAC-SHA256 over
  metoden, stien, forespørgslen og brødteksten, med din **organisations-webhookhemmelighed** som nøgle.
  Verificer denne, og afvis forespørgsler, der er mere end 5 minutter gamle; se
  [Verificer webhooksignaturer](/da/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — ældre HMAC-SHA256 af de nøjagtige
  byte i forespørgselsbrødteksten, med samme nøgle, sendes stadig til eksisterende verifikatorer
- `X-ThunderPhone-Call-ID` — Det aktuelle opkalds-ID

`Content-Type: application/json` angives, medmindre dit `endpoint.headers`
overskriver det — en brugerdefineret `Content-Type` har forrang.

<Warning>
  Signaturen bruger webhookhemmeligheden på organisationsniveau fra
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook) som nøgle.
  Hvis din organisation aldrig har konfigureret den ældre webhook, findes der
  ingen hemmelighed, og værktøjskald indeholder **kun** `X-ThunderPhone-Call-ID` —
  en handler, der fejler hårdt ved manglende signatur, vil afvise dem.
  Konfigurer enten den ældre webhook for at få en hemmelighed, eller angiv din egen
  delte hemmelighed i `endpoint.headers`.
</Warning>

### Anmodningsformat

Hver API-forbindelse har en indstilling for `request_format`. Vælg `vapi` eller
`retell`, når et importeret værktøj fortsat skal kalde et endpoint, der er bygget til
den pågældende platform. Nye og eksisterende forbindelser bruger som standard `standard`.

For `POST` / `PUT` / `PATCH` serialiserer ThunderPhone den valgte
indpakning kanonisk (sorterede nøgler, kompakte skilletegn). Begge signaturer
dækker disse nøjagtige bytes.

#### Standard

`standard` sender kun værktøjsargumenterne uden wrapper. Dette er
standarden og bevarer ThunderPhone's oprindelige anmodningsbrødtekst byte for byte:

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

#### Vapi

`vapi` sender Vapis [`tool-calls`-servermeddelelse](https://docs.vapi.ai/tools/custom-tools)
i det format, Vapi dokumenterer. `call.id` er ThunderPhone-opkalds-ID'et. Værktøjskaldets
`id` er unikt for påkaldelsen og genbruges til at matche endpointets
resultat. Opkaldet vises både i `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-specifikke felter som `assistant`, `artifact` og det fulde `call`-objekt
sendes ikke.

Returner Vapis svarindpakning med det matchende værktøjskalds-ID:

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

ThunderPhone giver AI'en den udpakkede `result`-streng. Den accepterer også en
JSON-værdi for kompatibilitet med endpoints, der allerede returnerer en. En
`error` i stedet for `result` returneres til AI'en som en værktøjsfejl. Hvis
endpointet returnerer en forkert formateret eller ikke-matchende Vapi-indpakning, logger ThunderPhone
en advarsel og falder tilbage til sin standardfortolkning af svar i stedet for at afbryde
opkaldet.

#### Retell

`retell` sender anmodningen, der er dokumenteret for en [Retell-tilpasset
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 indeholder kun `call_id`. ThunderPhone inkluderer ikke
Retell-specifikke felter, som den ikke reelt kan levere ved afsendelse, såsom
`agent_id`, `transcript`, `transcript_object`, `metadata` eller dynamiske
variabler. Retell accepterer enhver svartekst; ThunderPhone videresender den
ved hjælp af standardhåndteringen af svar.

For `GET` / `DELETE` sendes argumenterne som **queryparametre**
og brødteksten er tom uanset `request_format`. V2-signaturen
dækker querystrengen, som den sendes; den ældre signatur beregnes over
den tomme bytestreng. Se
[Verificer webhook-signaturer](/da/guides/verify-webhook-signatures).

### Svar

Returner et JSON-svar med værktøjsresultatet:

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

Svaret formateres og gives til AI'en, så den kan fortsætte
samtalen. Ikke-JSON-svar indpakkes som `{"data": "<text>"}`;
timeouts og forbindelsesfejl rapporteres til AI'en som fejl, så
agenten kan undskylde og fortsætte i stedet for at gå i stå.

## Afsendelse i webhooktilstand

Værktøjer **uden** et `endpoint` sendes til din organisations ældre
webhook-URL som en signeret `telephony.tool`-anmodning (telefonopkald) eller
`web.tool`-anmodning (webopkald). I modsætning til
[revisionsnotifikationerne](/da/webhooks/events), der leveres til webhookendpoints efter
udførelse, **er** denne anmodning udførelsen — dit HTTP-svar er
værktøjsresultatet.

```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` indeholder `origin_domain` i stedet for `from_number` /
`to_number`. Svar med værktøjsresultatet som JSON — samme svarformat
som ved direkte endpointkald. Anmodningen er signeret med organisationens
webhook-hemmelighed og begge signaturheadere, ligesom alle andre webhooks.

<Note>
  Abonnerede [webhookendpoints](/da/webhooks/endpoints) modtager desuden en
  ikke-blokerende `telephony.tool` / `web.tool`-**notifikation efter**
  hvert værktøj udføres (uanset hvilken sti der kørte det), inklusive
  værktøjets svar — nyttigt til revisionsspor. Se
  [hændelseskataloget](/da/webhooks/events).
</Note>

---

## Signaturverificering

Direkte værktøjskald signeres på samme måde som webhooks med din
organisations webhook-hemmelighed:

- `X-ThunderPhone-Signature-V2` signerer tidsstemplet, metoden, stien og
  forespørgselsstrengen som sendt samt de nøjagtige bytes i brødteksten. Afvis anmodninger, hvis
  deres tidsstempel afviger mere end 5 minutter fra dit ur.
- `X-ThunderPhone-Signature` (forældet) signerer kun bytes i brødteksten og den
  tomme bytestreng for `GET` / `DELETE`. Den sendes stadig uændret.

SDK-hjælpefunktionerne kontrollerer V2-headeren, herunder 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>

Fuldstændige opskrifter — den signerede streng, et gennemarbejdet eksempel, kode uden
SDK'et, tilfældet med `GET` / `DELETE` og forbeholdet om ingen hemmelighed — findes i [Bekræft webhook-signaturer](/da/guides/verify-webhook-signatures).

---

## Eksempel: Komplet bookingflow

Her er et sæt værktøjer til et komplet system til tidsbestilling:

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

---

## Bedste praksis

<AccordionGroup>
  <Accordion title="Skriv klare beskrivelser">
    Feltet `description` hjælper AI'en med at forstå, **hvornår** værktøjet skal bruges. Vær specifik om, hvad det gør, og hvornår det er relevant.
  </Accordion>

  <Accordion title="Håndter fejl elegant">
    Returnér fejlmeddelelser, som AI'en kan forstå: `{"error": "No slots available for that date"}` frem for generiske 500-fejl.
  </Accordion>

  <Accordion title="Hold svar korte">
    Returnér kun det, AI'en har brug for, for at fortsætte samtalen. Store payloads gør svartiderne langsommere.
  </Accordion>

  <Accordion title="Brug obligatoriske felter med omtanke">
    Markér kun felter som `required`, når det er absolut nødvendigt. AI'en vil bede brugeren om obligatoriske oplysninger, før værktøjet kaldes.
  </Accordion>
</AccordionGroup>

---

## Relateret

<CardGroup cols={2}>
  <Card title="Indbyggede værktøjer" icon="wrench" href="/da/guides/built-in-tools">
    Anmod om platformadministrerede opkaldshandlinger uden at definere et endpoint.
  </Card>
  <Card title="Appforbindelser" icon="plug" href="/da/guides/connect-apps">
    Platformadministrerede værktøjer til HubSpot, Salesforce, Slack, Google
    Kalender, Google Sheets og Cal.com — intet endpoint påkrævet.
  </Card>
  <Card title="MCP-servere" icon="server" href="/da/guides/mcp-servers">
    Tilknyt en MCP-server, og lad agenten kalde dens værktøjer.
  </Card>
  <Card title="API-forbindelser" icon="code" href="/da/guides/api-connections">
    Genanvendelige REST-integrationer, som du kan tilknytte agenter.
  </Card>
  <Card title="Bekræft webhook-signaturer" icon="shield-check" href="/da/guides/verify-webhook-signatures">
    Én bekræftelseshjælper til webhooks og værktøjskald.
  </Card>
</CardGroup>
