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

# Dynamisk konfigurasjon per samtale

> Velg en agent – eller skriv om en prompt – for hvert innkommende anrop basert på tilpasset logikk i en webhook.

Som standard har hvert telefonnummer og hver publiserbare nøkkel en fast agent
tilordnet. Når du trenger tilpasning **per innringer** eller **per besøkende**
— VIP-ruting, kontekst for innloggede brukere, A/B-tester av ledetekster — bytt til
webhookmodus og la 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 opptil
   10 sekunder på svaret ditt før samtalen fortsetter.
2. ThunderPhone sender deg `{call_id, from_number, to_number}` (widget-
   økter inneholder widgetspesifikke felt i stedet for numre — se
   [forespørselsskjemaet](/nb/webhooks/call-incoming)).
3. Serveren din svarer med en agentkonfigurasjon (ledetekst, stemme,
   produkt, verktøy). ThunderPhone bruker den konfigurasjonen for samtalen.
4. Hvis du returnerer `{}`, får tidsavbrudd eller en feil, brukes den fast
   tilordnede agenten som reserve. Trygg standard.

<Note>
  Fungerer identisk 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>

## 1. Konfigurer webhook-destinasjonen

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

    ```bash theme={null}
    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 inneholder en engangs-`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 theme={null}
    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 hver øktstart.
  </Tab>
</Tabs>

## 2. Implementer håndteringsfunksjonen

Tre tommelfingerregler:

* **Verifiser signaturen** på 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**. Ti sekunder er den absolutte grensen, og hvert sekund er
  stillhet for innringeren. Gjør databaseoppslag hvis du trenger det, 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**. Enhver uventet tilstand skal returnere `{}` slik at
  den statisk tildelte agenten håndterer samtalen.

<CodeGroup>
  ```python FastAPI theme={null}
  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 theme={null}
  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 samtaler](/nb/webhooks/call-incoming).
De mest brukte feltene:

| Felt                          | Type             | Beskrivelse                                                                         |
| ----------------------------- | ---------------- | ----------------------------------------------------------------------------------- |
| `prompt`                      | string (påkrevd) | Systemprompt for agenten                                                            |
| `voice`                       | string (påkrevd) | Stemme-ID fra [`GET /v1/voices`](/api-reference/agents#voices)                      |
| `product`                     | string           | Standardverdien er `spark`                                                          |
| `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 samtale og `max_hold_seconds` er ikke tilgjengelige i
  webhooksvaret. Angi dem på
  [agenten](/api-reference/agents) du refererer til.
</Note>

## Mønstre

### Kontekst for innloggede brukere

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

### A/B-utrulling av prompt

Før du implementerer dette selv, merk at ThunderPhone har en innebygd
[Eksperimenter](/nb/guides/concepts)-funksjon
(`/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 DB, og korreler den senere med vurderingen av
det fullførte anropet.

### Tidsbasert ruting

Åpningstid → stemmeagent for «direkte kundestøtte»; utenfor åpningstid → stemmeagent for «ta imot en beskjed».
Rent bytte basert på `new Date().getUTCHours()` i behandleren din.

***

## Neste steg

<CardGroup cols={2}>
  <Card title="Referanse for webhook ved innkommende anrop" icon="phone" href="/nb/webhooks/call-incoming">
    Nøyaktige skjemaer for forespørsler og svar, inkludert alle konfigurasjonsnøkler.
  </Card>

  <Card title="Verifiser webhook-signaturer" icon="shield-check" href="/nb/guides/verify-webhook-signatures">
    Få HMAC riktig én gang, og gjenbruk det 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>
