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

# Funksjonsverktøy

> La AI-agentene dine kalle eksterne API-er under samtaler

Funksjonsverktøy lar AI-agentene 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

<Note>
  Funksjonsverktøy er alternativet der du bruker ditt eget API. ThunderPhone
  leverer også plattformadministrerte verktøy som ikke trenger noe endepunkt:
  [appkoblinger](/nb/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [API-koblinger](/nb/guides/api-connections) og
  [MCP-servere](/nb/guides/mcp-servers).
</Note>

***

## Verktøyskjema

Hvert verktøy følger denne strukturen:

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

### Funksjonsdefinisjon

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

### Endepunktkonfigurasjon

| Felt      | Type   | Påkrevd | Beskrivelse                               |
| --------- | ------ | ------- | ----------------------------------------- |
| `url`     | string | Ja      | URL-en til API-endepunktet ditt           |
| `method`  | string | Nei     | HTTP-metode (standard: `POST`)            |
| `headers` | object | 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 kallingsveier

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) |
| Body                      | **Kun verktøyargumenter**                                                       | `telephony.tool` / `web.tool`-omslag                                                        |
| Headere                   | Dine `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature`                                                 |
| Signeringsnøkkel          | Organisasjonens webhook-hemmelighet                                             | Organisasjonens webhook-hemmelighet                                                         |

Begge veier er **blokkerende** — AI-en venter midt i en setning på
resultatet — med en tidsavbruddsgrense på **20 s**. Hold handlerne raske. En blanding fungerer fint:
i en samtale der organisasjonen har en webhook-URL, blir verktøy med et `endpoint`
kalt direkte, mens resten faller tilbake til webhooken.

## Direkte endepunktkall

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

### Forespørselsheadere

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

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

* `X-ThunderPhone-Signature` — HMAC-SHA256 av de nøyaktige bytene i
  forespørselsbrødteksten, med organisasjonens **webhook-hemmelighet**
  som nøkkel
* `X-ThunderPhone-Call-ID` — ID-en til den gjeldende samtalen

`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) som nøkkel.
  Hvis organisasjonen din aldri har konfigurert den eldre webhooken, finnes
  det ingen hemmelighet, og verktøykall inneholder **kun**
  `X-ThunderPhone-Call-ID` — en behandler som feiler ved manglende
  signatur, vil avvise dem.
  Konfigurer enten den eldre webhooken for å få en hemmelighet, eller legg
  din egen delte hemmelighet i `endpoint.headers`.
</Warning>

### Forespørselsbrødtekst

For `POST` / `PUT` / `PATCH` inneholder brødteksten **kun**
verktøyargumentene (ingen innpakning), serialisert kanonisk (sorterte
nøkler, kompakte skilletegn):

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

For `GET` / `DELETE` sendes argumentene som **spørringsparametere**,
og brødteksten er tom — signaturen beregnes da over den tomme
bytestrengen. Se
[Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures).

### Svar

Returner et JSON-svar med verktøyresultatet:

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

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

## Utsending i webhook-modus

Verktøy **uten** et `endpoint` sendes til organisasjonens eldre
webhook-URL som en signert `telephony.tool`-forespørsel (telefonsamtaler)
eller `web.tool`-forespørsel (nettsamtaler). I motsetning til
[revisjonsvarslingene](/nb/webhooks/events) som leveres til webhook-endepunkter
etter kjøring, **er** denne forespørselen selve kjøringen — HTTP-svaret
ditt er verktøyresultatet.

```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` inneholder `origin_domain` i stedet for `from_number` /
`to_number`. Svar med verktøyresultatet som JSON — samme svarkontrakt
som for direkte endepunktkall. Forespørselen signeres med
organisasjonens webhook-hemmelighet over den rå brødteksten, 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 kjøres (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:

* HMAC-SHA256 over de nøyaktige byteverdiene i forespørselskroppen (den kanoniske JSON-en — sorterte nøkler, ingen ekstra mellomrom)
* Med organisasjonens webhook-hemmelighet som nøkkel
* `GET`- / `DELETE`-verktøy signerer den tomme byte-strengen

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

Fullstendige eksempler — inkludert tilfellet med tom forespørselskropp og forbeholdet om manglende hemmelighet — finner du i [Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures).

***

## Eksempel: Komplett bestillingsflyt

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

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

***

## 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 er passende å bruke det.
  </Accordion>

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

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

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

***

## Relatert

<CardGroup cols={2}>
  <Card title="Appkoblinger" icon="plug" href="/nb/guides/connect-apps">
    Plattformadministrerte verktøy for HubSpot, Salesforce, Slack, Google
    Kalender, Google Sheets 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-koblinger" icon="code" href="/nb/guides/api-connections">
    Gjenbrukbare REST-integrasjoner som du kan koble til agenter.
  </Card>

  <Card title="Bekreft webhook-signaturer" icon="shield-check" href="/nb/guides/verify-webhook-signatures">
    Én bekreftelseshjelper for webhooks og verktøykall.
  </Card>
</CardGroup>
