---
title: "Dynamisk konfigurasjon per samtale"
description: "Velg hvilken stemmeagent som svarer – eller skriv om ledeteksten og innstillingene – separat for hvert innkommende anrop, styrt av tilpasset logikk i en webhook du kontrollerer."
---

Som standard har hvert telefonnummer og hver publiserbare nøkkel en statisk
agent tilordnet. Når du trenger tilpasning **per innringer** eller **per besøkende**
— VIP-ruting, kontekst for innloggede brukere, A/B-tester av ledetekster — bytter du til
webhook-modus og lar serveren din avgjøre.

## Slik fungerer det

1. Du abonnerer på hendelsen [`telephony.incoming`](/nb/webhooks/events)
   (telefon) eller [`web.incoming`](/nb/webhooks/events) (widget).
   Begge er **blokkerende** webhooker: ThunderPhone venter på svaret ditt
   før samtalen fortsetter, i opptil 13 sekunder for en telefonsamtale
   (inkludert ett nytt forsøk) og 10 sekunder for en widgetøkt.
2. ThunderPhone sender deg `{call_id, from_number, to_number}` (widgetøkter
   har widgetspesifikke felt i stedet for numre — se
   [forespørselsskjemaet](/nb/webhooks/call-incoming)).
3. Serveren din svarer med en agentkonfigurasjon (prompt, stemme,
   produkt, verktøy). ThunderPhone bruker denne konfigurasjonen for samtalen.
4. Det finnes ingen reserveagent. Hvis du returnerer `{}` eller en ugyldig
   konfigurasjon, eller forespørselen fortsatt mislykkes etter sitt
   [nye forsøk](/nb/webhooks/call-incoming), avsluttes
   telefonsamtalen umiddelbart og registreres med `end_reason`
   `inbound_config_webhook_failed`; en forespørsel om widgetøkt mislykkes.

<Note>
  Fungerer på samme måte for telefonsamtaler (`telephony.incoming`) og widget-
  økter (`web.incoming`), enten de leveres til et webhook-endepunkt
  eller til den eldre webhooken med én URL.
</Note>

<Warning>
  **Ingen samtykkekunngjøring fra ThunderPhone spilles av for samtaler som er
  konfigurert via innebygd webhook.** Denne banen omgår kunngjøringen ved
  samtalestart på agentnivå og er uttrykkelig unntatt fra ThunderPhones rammeverk
  for samtykkekunngjøringer (vilkår for bruk, delen «Opptak og samtykke»). Organisasjonen
  din har ansvar for alle varsler og samtykker knyttet til opptak, overvåking,
  KI-deltakelse og identifisering av innringere i disse samtalene. Oppgi
  dette i din egen flyt — for eksempel i åpningsskriptet i ledeteksten —
  før du aktiverer innebygd webhook-modus. Et webhook-svar som refererer til en
  lagret `agent_id`, bruker den agentens vanlige policy for opptak og opplysninger.
</Warning>

## 1. Konfigurer webhook-destinasjonen

<Tabs>
<Tab title="Telefonsamtaler">
For telefonnumre abonnerer du endepunktet ditt på `telephony.incoming`:

```bash
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'
```

Svaret inkluderer en engangshemmelighet, `secret` — lagre den; du bruker den
til signaturverifisering.
</Tab>
<Tab title="Webwidget">
For widget-økter oppretter du en publiserbar nøkkel i `mode="webhook"`
med endepunktets URL innebygd:

```bash
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'
```

Widgeten sender en POST-forespørsel til denne URL-en ved starten av hver økt.
</Tab>
</Tabs>

## 2. Implementer behandleren

Tre tommelfingerregler:

- **Verifiser signaturen** for hver forespørsel (se
  [Verifiser webhook-signaturer](/nb/guides/verify-webhook-signatures)).
  Ikke hopp over dette i utvikling — gjør det riktig én gang, og gjenbruk det.
- **Svar raskt**. En telefonsamtales første forsøk avbrytes etter 10
  sekunder, og hele utvekslingen etter 13, og hvert sekund er stillhet
  for innringeren. Slå opp i databasen hvis du må, men ikke kall nedstrøms-LLM-er synkront — hvis du vil ha dynamisk
  promptgenerering, forhåndsberegn og mellomlagre.
- **Fall tilbake på en ryddig måte**. For enhver uventet tilstand, svar med en
  standard `prompt` og `voice` (eller en lagret `agent_id`). Å returnere `{}`
  eller en feilstatus avslutter samtalen.

<CodeGroup>
```python FastAPI
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")

@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)

    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default

    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs

def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
```

```javascript Express
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

app.post(
  "/thunderphone/incoming",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});

    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);
```
</CodeGroup>

## 3. Svarskjema

Svarteksten samsvarer nøyaktig med
[svarskjemaet for innkommende anrop](/nb/webhooks/call-incoming).
De vanligste feltene:

| Felt | Type | Beskrivelse |
|-------|------|-------------|
| `prompt` | string (påkrevd) | Systeminstruks for agenten |
| `voice` | string (påkrevd) | Stemme-ID fra [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | string | Standardverdien er `spark`. Eksperimentelle produkter er bare tilgjengelige for organisasjoner på tillatelseslisten. |
| `background_track` | string \| null | ID for bakgrunnslyd |
| `acknowledgement_prompt_mode` | string | `auto` eller `manual` (kun Storm-med-bekreftelse) |
| `acknowledgement_prompt` | string | Påkrevd når modusen er `manual` |
| `tools` | array | Innebygde skjemaer for funksjonsverktøy — se [Funksjonsverktøy](/nb/tools/overview) |

<Note>
  Talerekkefølge per anrop og `max_hold_seconds` er ikke tilgjengelige i
  webhook-svaret. Angi dem på
  [agenten](/api-reference/agents) du refererer til.
</Note>

### Behold en lagret agent og oppgi variabler

Returner `{"agent_id": 12, "variables": {"name": "Ada"}}` for å bruke organisasjonens
lagrede agent med data per samtale. Ledeteksten kan inneholde `{{name}}` eller
`{{name|Friend}}`. Webhook-variabler slås sammen med variabler på forespørselsnivå; null
bruker plassholderens standardverdi, eller tom tekst hvis ingen er angitt. De endelige
verdiene og uløste navnene vises i samtaledetaljer og fullføringswebhooker.
Svar for lagrede agenter godtar bare `agent_id`, `variables` og `timezone`. Hvis `prompt` er
til stede, bruker svaret innebygd konfigurasjon og ignorerer `agent_id` (inkludert
null eller metadata som ikke er heltall); den innebygde ledeteksten må fortsatt være gyldig.
Svar med innebygd konfigurasjon kan også inkludere `variables`. Svar for lagrede agenter bruker
agentens distribuerte A/B-splitt for både telefon- og widgetanrop, og gjengir deretter variabler.
Se [samtalevariabler](/nb/guides/call-variables) for grenser og støtte for økt-API. Blokkerende
konfigurasjon kommer fra den eldre URL-en for telefonnummer/organisasjon eller en widgetnøkkel i webhook-modus;
innkommende hendelser i endepunktsystemet er kun varsler.

### Angi tidssonen per samtale

Én stemmeagent kan svare for kontakter i ulike tidssoner. Legg til `timezone`, et
IANA-navn, i en lagret agent eller et innebygd svar, så oppgir agenten dato og
klokkeslett og tilbyr tider i den sonen for denne samtalen, foran agentens egen
tidssone:

```json
{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}
```

Send sonen i stedet for et tidsstempel i `variables`: Agentens klokke fortsetter
å gå under samtalen, og et klokkeslett du sender inn blir utdatert. En verdi som
ikke er en IANA-sone, ignoreres, og samtalen kobles til i agentens tidssone.

## Mønstre

### Kontekst for innlogget bruker

I widgeter i webhook-modus vet den besøkendes side allerede hvem de
er. Kall webhooken din med en parameter i spørrestrengen som widget-SDK-en
videresender (`?customer_id=123`), og slå opp kunden på serversiden.

### A/B-lansering av prompter

Før du implementerer dette selv, merk at ThunderPhone har en innebygd
funksjon for [Eksperimenter](/nb/guides/concepts)
(`/dashboard/experiments` og **A/B**-fanen i agentbyggeren) som
definerer varianter, fordeler trafikk og sammenligner resultater per variant —
ingen webhook kreves.

Hvis du likevel trenger kontroll på webhook-siden: hash `call_id` → bøtte;
server prompt A for `0..49` og prompt B for `50..99`. Registrer hvilken
bøtte du valgte i din egen database, og korreler den senere med vurderingen
av det fullførte anropet.

### Tidsbasert ruting

Åpningstid → agent for "direktesupport"; utenfor åpningstid → agent for "ta imot en beskjed".
En enkel byttefunksjon basert på `new Date().getUTCHours()` i behandleren din.

---

## Neste steg

<CardGroup cols={2}>
  <Card title="Referanse for webhook for innkommende anrop" icon="phone" href="/nb/webhooks/call-incoming">
    Nøyaktige skjemaer for forespørsel og svar, inkludert alle konfigurasjonsnøkler.
  </Card>
  <Card title="Verifiser webhook-signaturer" icon="shield-check" href="/nb/guides/verify-webhook-signatures">
    Konfigurer HMAC riktig én gang; gjenbruk overalt.
  </Card>
  <Card title="Bygg en verktøyintegrasjon" icon="screwdriver-wrench" href="/nb/guides/build-tool-integration">
    Kombiner dynamisk ruting med verktøy per agent.
  </Card>
  <Card title="Leveringssemantikk" icon="bolt" href="/nb/webhooks/overview">
    Nye forsøk, rekkefølge, tidsavbrudd.
  </Card>
</CardGroup>
