> ## 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 konfiguration pr. opkald

> Vælg en agent – eller omskriv en prompt – for hvert indgående opkald baseret på tilpasset logik i en webhook.

Som standard har hvert telefonnummer og hver offentlig nøgle en statisk agent
tilknyttet. 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 afgøre det.

## 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 op til
   10 sekunder på dit svar, før opkaldet fortsætter.
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. Hvis du returnerer `{}`, får timeout eller en fejl, bruges den statisk tilknyttede
   agent som fallback. Sikker standard.

<Note>
  Fungerer identisk for telefonopkald (`telephony.incoming`) og widget-
  sessioner (`web.incoming`), uanset om de leveres til et webhook-endpoint
  eller til den ældre webhook med én URL.
</Note>

## 1. Konfigurer webhookdestinationen

<Tabs>
  <Tab title="Telefonopkald">
    For telefonnumre skal du abonnere dit endpoint 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 indeholder en `secret` til engangsbrug — gem den; du skal bruge den
    til signaturverificering.
  </Tab>

  <Tab title="Webwidget">
    For widgetsessioner skal du oprette en offentlig nøgle i `mode="webhook"`
    med dit endpoint-URL indbygget:

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

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

## 2. Implementer håndteringsfunktionen

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**. Ti sekunder er den hårde grænse, og hvert sekund er
  stilhed for den, der ringer. Udfør 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**. Enhver uventet tilstand skal returnere `{}`, så
  den statisk tildelte agent håndterer opkaldet.

<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. 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`                                                                     |
| `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`                                                    |
| `tools`                       | array             | Indlejrede skemaer for funktionsværktøjer — se [Funktionsværktøjer](/da/tools/overview) |

<Note>
  Talerækkefølge pr. opkald og `max_hold_seconds` er ikke tilgængelige i
  webhook-svaret. Indstil dem på den
  [agent](/api-reference/agents), du refererer til.
</Note>

## Mønstre

### Kontekst for logget ind-bruger

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

### A/B-udrulning af prompts

Før du bygger dette selv, skal du bemærke, at ThunderPhone har en indbygget
[Experiments](/da/guides/concepts)-funktion
(`/dashboard/experiments` og agentbyggerens **A/B**-fane), som
definerer varianter, fordeler trafik og sammenligner resultater pr. variant —
ingen webhook nødvendig.

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, hvilken
bucket du valgte, i din egen database, og korreler senere med det
afsluttede opkalds vurdering.

### Tidsbaseret routing

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

***

## Næste trin

<CardGroup cols={2}>
  <Card title="Webhook-reference for indgående opkald" icon="phone" href="/da/webhooks/call-incoming">
    Præcise request- og response-skemaer, inklusive alle konfigurationsnøgler.
  </Card>

  <Card title="Bekræft webhook-signaturer" icon="shield-check" href="/da/guides/verify-webhook-signatures">
    Få HMAC rigtigt én gang, og genbrug det overalt.
  </Card>

  <Card title="Byg en værktøjsintegration" icon="screwdriver-wrench" href="/da/guides/build-tool-integration">
    Kombiner 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>
