> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Funktionsværktøjer

> Lad dine AI-agenter kalde eksterne API'er under samtaler

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

<Note>
  Funktionsværktøjer er løsningen, hvor du bruger din egen API. ThunderPhone
  leverer også platformadministrerede værktøjer, der ikke kræver noget endpoint:
  [appforbindelser](/da/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [API-forbindelser](/da/guides/api-connections) og
  [MCP-servere](/da/guides/mcp-servers).
</Note>

***

## Værktøjsskema

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

```json theme={null}
{
  "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"
    }
  }
}
```

### Funktionsdefinition

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

### Endpoint-konfiguration

| Felt      | Type   | Påkrævet | Beskrivelse                             |
| --------- | ------ | -------- | --------------------------------------- |
| `url`     | string | Ja       | URL'en til dit API-endpoint             |
| `method`  | string | Nej      | HTTP-metode (standard: `POST`)          |
| `headers` | object | Nej      | Tilpassede headers, 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                   | **Rene værktøjsargumenter**                                                     | `telephony.tool` / `web.tool`-indpakning                                                      |
| Headers                     | Dine `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature` | `Content-Type` + `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 — med en timeout på **20 s**. Hold handlers hurtige. En
blanding fungerer fint: Under et opkald, hvor organisationen 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 theme={null}
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

Brugerdefinerede headere fra dit `endpoint.headers` inkluderes altid
ordret samt to ThunderPhone-navnerumsheadere:

* `X-ThunderPhone-Signature` — HMAC-SHA256 af de nøjagtige bytes i
  anmodningsbrødteksten med din **organisations webhookhemmelighed**
  som nøgle
* `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 en 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>

### Anmodningsbrødtekst

For `POST` / `PUT` / `PATCH` indeholder brødteksten **kun**
værktøjsargumenterne (ingen indpakning), serialiseret kanonisk (sorterede
nøgler, kompakte separatorer):

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

For `GET` / `DELETE` sendes argumenterne som **forespørgselsparametre**
og brødteksten er tom — signaturen beregnes derefter over den tomme
bytestreng. Se
[Verificer webhooksignaturer](/da/guides/verify-webhook-signatures).

### Svar

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

```json theme={null}
{
  "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
[auditnotifikationerne](/da/webhooks/events), der leveres til webhookendpoints
efter udførelse, **er** denne anmodning udførelsen — dit HTTP-svar er
værktøjsresultatet.

```json theme={null}
{
  "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 — den samme svarkontrakt
som ved direkte endpointkald. Anmodningen signeres med organisationens
webhookhemmelighed over den rå brødtekst, ligesom alle andre webhooks.

<Note>
  Abonnerede [webhookendpoints](/da/webhooks/endpoints) modtager desuden
  en ikke-blokerende `telephony.tool` / `web.tool`-**notifikation
  efter** hver værktøjsudførelse (uanset hvilken sti der udførte den),
  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:

* HMAC-SHA256 over de nøjagtige bytes i request-bodyen (den kanoniske JSON —
  sorterede nøgler, ingen ekstra mellemrum)
* Med din organisations webhook-hemmelighed som nøgle
* `GET`- / `DELETE`-værktøjer signerer den tomme bytestreng

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_tool_call(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, signature)

  @app.post("/appointments/search")
  async def search_appointments(request: Request):
      body = await request.body()
      signature = request.headers.get("X-ThunderPhone-Signature", "")

      if not verify_tool_call(body, signature, WEBHOOK_SECRET):
          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 theme={null}
  app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-thunderphone-signature'] || '';
    const expected = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!signature ||
        signature.length !== expected.length ||
        !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
      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>

Komplette opskrifter — inklusive tilfældet med tom body og forbeholdet om manglende hemmelighed —
findes i [Verificer 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 theme={null}
{
  "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 korrekt">
    Returner fejlmeddelelser, som AI'en kan forstå: `{"error": "No slots available for that date"}` i stedet for generiske 500-fejl.
  </Accordion>

  <Accordion title="Hold svar korte">
    Returner kun det, AI'en har brug for for at fortsætte samtalen. Store payloads sænker svartiderne.
  </Accordion>

  <Accordion title="Brug påkrævede felter med omtanke">
    Markér kun felter som `required`, når det er strengt nødvendigt. AI'en beder brugeren om påkrævede oplysninger, før den kalder værktøjet.
  </Accordion>
</AccordionGroup>

***

## Relateret

<CardGroup cols={2}>
  <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, 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>
