---
title: "Dynamisk konfiguration pr. opkald"
description: "Vælg den svarende agent — eller omskriv dens prompt og indstillinger — separat for hvert indgående opkald, styret af brugerdefineret logik i en webhook, du kontrollerer."
---

Som standard har hvert telefonnummer og hver publicerbar nøgle en statisk
agent tildelt. Når du har brug for tilpasning **pr. opkalder** eller
**pr. besøgende** — VIP-routing, kontekst for indloggede brugere,
A/B-tests af prompts — skal du skifte til webhooktilstand og lade din
server træffe beslutningen.

## Sådan fungerer det

1. Du abonnerer på hændelsen [`telephony.incoming`](/da/webhooks/events)
   (telefon) eller [`web.incoming`](/da/webhooks/events) (widget).
   Begge er **blokerende** webhooks: ThunderPhone venter på dit svar,
   før opkaldet fortsætter, i op til 13 sekunder for et telefonopkald
   (inklusive ét genforsøg) og 10 sekunder for en widgetsession.
2. ThunderPhone sender dig `{call_id, from_number, to_number}` (widget-
   sessioner indeholder widgetspecifikke felter i stedet for numre — se
   [anmodningsskemaet](/da/webhooks/call-incoming)).
3. Din server svarer med en agentkonfiguration (prompt, stemme,
   produkt, værktøjer). ThunderPhone bruger den konfiguration til opkaldet.
4. Der er ingen reserveagent. Hvis du returnerer `{}` eller en ugyldig
   konfiguration, eller hvis anmodningen stadig fejler efter sit
   [genforsøg](/da/webhooks/call-incoming), afsluttes
   telefonopkaldet straks og registreres med `end_reason`
   `inbound_config_webhook_failed`; en widgetsessionanmodning fejler.

<Note>
  Fungerer på samme måde for telefonopkald (`telephony.incoming`) og widget-
  sessioner (`web.incoming`), uanset om de leveres til et webhook-endepunkt
  eller til den ældre webhook med enkelt URL.
</Note>

<Warning>
  **Ingen ThunderPhone-samtykkemeddelelse afspilles ved opkald, der konfigureres
  inline via webhook.** Denne sti omgår meddelelsen ved opkaldsstart på
  agentniveau og er udtrykkeligt undtaget fra ThunderPhones ramme for
  samtykkemeddelelser (servicevilkår, afsnittet "Optagelse og samtykke"). Din
  organisation har ansvaret for alle meddelelser og samtykker vedrørende
  optagelse, overvågning, AI-deltagelse og opkalderidentifikation for disse
  opkald. Angiv dem i dit eget flow — for eksempel i promptens åbningsscript —
  før du aktiverer inline webhooktilstand. Et webhook-svar, der refererer til et
  gemt `agent_id`, bruger den pågældende agents normale politik for optagelse og
  oplysning.
</Warning>

## 1. Konfigurer webhookdestinationen

<Tabs>
<Tab title="Telefonopkald">
For telefonnumre skal du abonnere dit endepunkt 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 indeholder en `secret`, der kun vises én gang — gem den; du skal bruge den
til signaturverifikation.
</Tab>
<Tab title="Webwidget">
For widgetsessioner skal du oprette en publicerbar nøgle i `mode="webhook"`
med din endepunkt-URL indbygget:

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

Widgetten sender en POST-anmodning til denne URL ved starten af hver session.
</Tab>
</Tabs>

## 2. Implementer handleren

Tre tommelfingerregler:

- **Bekræft signaturen** for hver anmodning (se
  [Bekræft webhook-signaturer](/da/guides/verify-webhook-signatures)).
  Spring ikke dette over i udvikling — gør det rigtigt én gang, og genbrug det.
- **Svar hurtigt**. Et telefonopkalds første forsøg afbrydes efter 10
  sekunder og hele udvekslingen efter 13, og hvert sekund er stilhed
  for opkalderen. Foretag databaseopslag, hvis du har brug for det, men kald
  ikke efterfølgende LLM'er synkront — hvis du vil have dynamisk
  promptgenerering, skal du forudberegne og cache.
- **Fald rent tilbage**. For enhver uventet tilstand skal du svare med en
  standard-`prompt` og `voice` (eller et gemt `agent_id`). Returnering af `{}`
  eller en fejlstatus afslutter opkaldet.

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

Svarbrødteksten matcher
[svarskemaet for indgående opkald](/da/webhooks/call-incoming)
præcist. De mest anvendte felter:

| Felt | Type | Beskrivelse |
|-------|------|-------------|
| `prompt` | streng (påkrævet) | Systemprompt til agenten |
| `voice` | streng (påkrævet) | Stemme-id fra [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | streng | Standard er `spark`. Eksperimentelle produkter er kun tilgængelige for organisationer på tilladelseslisten. |
| `background_track` | streng \| null | Id for baggrundslyd |
| `acknowledgement_prompt_mode` | streng | `auto` eller `manual` (kun Storm med bekræftelse) |
| `acknowledgement_prompt` | streng | Påkrævet, når tilstanden er `manual` |
| `language_switching` | streng | For flersprogede agenter: `ask_first` eller `follow_caller` |
| `tools` | array | Integrerede skemaer for funktionsværktøjer — se [Funktionsværktøjer](/da/tools/overview) |

<Note>
  Taleordning pr. opkald og `max_hold_seconds` er ikke tilgængelige i
  webhook-svaret. Angiv dem på den
  [agent](/api-reference/agents), du henviser til.
</Note>

### Behold en gemt agent, og angiv variabler

Returner `{"agent_id": 12, "variables": {"name": "Ada"}}` for at bruge den pågældende organisations
gemte agent med data pr. opkald. Dens prompt kan indeholde `{{name}}` eller
`{{name|Friend}}`. Webhook-variabler flettes med variabler på anmodningsniveau; null
bruger pladsholderens standardværdi eller tom tekst, hvis ingen er angivet. De endelige
værdier og uløste navne vises i opkaldsdetaljer og fuldførelses-webhooks.
Svar for gemte agenter accepterer kun `agent_id`, `variables` og `timezone`. Hvis `prompt` er
til stede, bruger svaret integreret konfiguration og ignorerer `agent_id` (herunder
null eller metadata, der ikke er et heltal); den integrerede prompt skal stadig være gyldig. Svar med integreret
konfiguration kan også indeholde `variables`. Svar for gemte agenter bruger
agentens implementerede A/B-fordeling på både telefon- og widgetopkald og gengiver derefter variabler.
Se [opkaldsvariabler](/da/guides/call-variables) for grænser og understøttelse af sessions-API.
Blokerende konfiguration kommer fra den ældre URL for telefonnummer/organisation eller en widgetnøgle i webhooktilstand;
indgående hændelser fra endpointsystemet er kun notifikationer.

### Angiv tidszonen pr. opkald

Én agent kan besvare opkald for kontakter i forskellige tidszoner. Tilføj `timezone`, et
IANA-navn, til et gemt agent- eller inline-svar, så angiver agenten dato og
klokkeslæt og tilbyder tidspunkter i den zone for dette opkald, forud for agentens egen
tidszone:

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

Send zonen i stedet for et tidsstempel i `variables`: agentens ur fortsætter
med at køre under opkaldet, og et tidspunkt, du sender med, bliver forældet. En værdi,
der ikke er en IANA-zone, ignoreres, og opkaldet forbindes i agentens zone.

## Mønstre

### Kontekst for bruger, der er logget ind

I widgets i webhooktilstand ved den besøgendes side allerede, hvem vedkommende
er. Kald din webhook med en querystring-parameter, som widget-SDK'et
videresender (`?customer_id=123`), og slå kunden op på serversiden.

### A/B-udrulning af prompts

Før du selv implementerer dette, skal du være opmærksom på, at ThunderPhone har en indbygget
funktion til [Eksperimenter](/da/guides/concepts)
(`/dashboard/experiments` og fanen **A/B** i agentbyggeren), som
definerer varianter, fordeler trafik og sammenligner resultater pr. variant —
ingen webhook påkrævet.

Hvis du alligevel har brug for kontrol på webhook-siden: hash `call_id` → bucket;
servér prompt A for `0..49` og prompt B for `50..99`. Registrer den
bucket, du valgte, i din egen database, og korrelér senere med det
afsluttede opkalds vurdering.

### Tidsbaseret routing

Åbningstid → agenten "live support"; uden for åbningstid → agenten "tag en besked".
Rent skift baseret på `new Date().getUTCHours()` i din handler.

---

## Næste trin

<CardGroup cols={2}>
  <Card title="Reference for webhook til indgående opkald" icon="phone" href="/da/webhooks/call-incoming">
    Præcise skemaer for anmodning og svar, herunder alle konfigurationsnøgler.
  </Card>
  <Card title="Bekræft webhook-signaturer" icon="shield-check" href="/da/guides/verify-webhook-signatures">
    Få HMAC korrekt én gang, og genbrug det overalt.
  </Card>
  <Card title="Byg en værktøjsintegration" icon="screwdriver-wrench" href="/da/guides/build-tool-integration">
    Kombinér dynamisk routing med værktøjer pr. agent.
  </Card>
  <Card title="Leveringssemantik" icon="bolt" href="/da/webhooks/overview">
    Genforsøg, rækkefølge, timeouts.
  </Card>
</CardGroup>
