---
title: "Dynamisk konfiguration per samtal"
description: "Välj den svarande röstagenten – eller skriv om dess prompt och inställningar – separat för varje inkommande samtal, styrt av anpassad logik i en webhook som du kontrollerar."
---

Som standard har varje telefonnummer och publicerbar nyckel en statisk agent
kopplad. När du behöver anpassning **per uppringare** eller **per besökare**
— VIP-dirigering, kontext för inloggade användare, A/B-tester av prompter — växlar du till
webhook-läge och låter din server avgöra.

## Så fungerar det

1. Du prenumererar på händelsen [`telephony.incoming`](/sv/webhooks/events)
   (telefon) eller [`web.incoming`](/sv/webhooks/events) (widget).
   Båda är **blockerande** webhookar: ThunderPhone väntar på ditt
   svar innan samtalet fortsätter, i upp till 13 sekunder för ett
   telefonsamtal (inklusive ett återförsök) och 10 sekunder för en widgetsession.
2. ThunderPhone skickar `{call_id, from_number, to_number}` till dig (widget-
   sessioner innehåller widgetspecifika fält i stället för nummer — se
   [begärandeschemat](/sv/webhooks/call-incoming)).
3. Din server svarar med en agentkonfiguration (prompt, röst,
   produkt, verktyg). ThunderPhone använder den konfigurationen för samtalet.
4. Det finns ingen reservagent. Om du returnerar `{}` eller en ogiltig
   konfiguration, eller om begäran fortfarande misslyckas efter sitt
   [återförsök](/sv/webhooks/call-incoming), avslutas
   telefonsamtalet direkt och registreras med `end_reason`
   `inbound_config_webhook_failed`; en begäran om widgetsessions misslyckas.

<Note>
  Fungerar på samma sätt för telefonsamtal (`telephony.incoming`) och widget-
  sessioner (`web.incoming`), oavsett om de levereras till en webhookslutpunkt
  eller till den äldre webhooken med en enda URL.
</Note>

<Warning>
  **Inget ThunderPhone-meddelande om samtycke spelas upp för samtal som
  konfigurerats direkt via webhook.** Den här vägen kringgår agentens meddelande
  vid samtalsstart och är uttryckligen undantagen från ThunderPhones ramverk för
  samtyckesmeddelanden (användarvillkor, avsnittet "Inspelning och samtycke"). Din
  organisation ansvarar för alla meddelanden och samtycken för inspelning,
  övervakning, AI-medverkan och identifiering av uppringaren i dessa samtal. Ge
  dem i ditt eget flöde — till exempel i promptens inledande manus —
  innan du aktiverar direkt webhook-läge. Ett webhooksvar som refererar till ett
  sparat `agent_id` använder agentens vanliga policy för inspelning och
  information.
</Warning>

## 1. Konfigurera webhookdestinationen

<Tabs>
<Tab title="Telefonsamtal">
För telefonnummer prenumererar du din slutpunkt 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 innehåller en `secret` som bara visas en gång — spara den; du använder den
för signaturverifiering.
</Tab>
<Tab title="Webbwidget">
För widgetsessioner skapar du en publicerbar nyckel i `mode="webhook"`
med din slutpunkts-URL inbäddad:

```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 skickar en POST-begäran till denna URL vid varje sessionsstart.
</Tab>
</Tabs>

## 2. Implementera hanteraren

Tre tumregler:

- **Verifiera signaturen** för varje begäran (se
  [Verifiera webhook-signaturer](/sv/guides/verify-webhook-signatures)).
  Hoppa inte över detta i utvecklingsmiljön — gör rätt en gång och återanvänd.
- **Svara snabbt**. Ett telefonsamtals första försök avbryts efter 10
  sekunder och hela utbytet efter 13, och varje sekund är tystnad
  för den som ringer. Gör databasuppslag om du behöver, men anropa inte
  efterföljande LLM:er synkront — om du vill generera dynamiska promptar,
  förberäkna och cachelagra.
- **Använd en ren reservlösning**. Vid varje oväntat tillstånd ska du svara med en
  standard-`prompt` och `voice` (eller ett sparat `agent_id`). Att returnera `{}`
  eller en felstatus avslutar samtalet.

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

Svarstexten matchar
[svarsschemat för inkommande samtal](/sv/webhooks/call-incoming)
exakt. De vanligaste fälten:

| Fält | Typ | Beskrivning |
|-------|------|-------------|
| `prompt` | sträng (obligatoriskt) | Systemprompt för agenten |
| `voice` | sträng (obligatoriskt) | Röst-ID från [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | sträng | Standardvärdet är `spark`. Experimentella produkter är endast tillgängliga för tillåtelselistaorganisationer. |
| `background_track` | sträng \| null | ID för bakgrundsljud |
| `acknowledgement_prompt_mode` | sträng | `auto` eller `manual` (endast Storm med bekräftelse) |
| `acknowledgement_prompt` | sträng | Krävs när läget är `manual` |
| `language_switching` | sträng | För flerspråkiga agenter: `ask_first` eller `follow_caller` |
| `tools` | matris | Inline-scheman för funktionsverktyg — se [Funktionsverktyg](/sv/tools/overview) |

<Note>
  Talordning per samtal och `max_hold_seconds` är inte tillgängliga i
  webhooksvaret. Ange dem för den
  [agent](/api-reference/agents) du refererar till.
</Note>

### Behåll en sparad agent och ange variabler

Returnera `{"agent_id": 12, "variables": {"name": "Ada"}}` för att använda organisationens
sparade agent med data per samtal. Dess prompt kan innehålla `{{name}}` eller
`{{name|Friend}}`. Webhook-variabler slås samman ovanpå variabler på förfrågningsnivå; null
använder platshållarens standardvärde eller tom text om inget anges. De slutliga
värdena och olösta namnen visas i samtalsdetaljer och webhooks för slutförande.
Svar för sparade agenter accepterar endast `agent_id`, `variables` och `timezone`. Om `prompt`
finns används infogad konfiguration i svaret och `agent_id` ignoreras (inklusive
null eller metadata som inte är heltal); den infogade prompten måste fortfarande vara giltig. Svar med infogad
konfiguration kan också innehålla `variables`. Svar för sparade agenter använder
agentens driftsatta A/B-fördelning för både telefon- och widget-samtal och återger sedan variablerna. Se [samtalsvariabler](/sv/guides/call-variables)
för gränser och stöd för sessions-API. Blockerande konfiguration kommer från den äldre
URL:en för telefonnummer/organisation eller en widgetnyckel i webhook-läge; inkommande händelser från slutpunktssystemet är endast aviseringar.

### Ställ in tidszon per samtal

En agent kan svara kontakter i olika tidszoner. Lägg till `timezone`, ett
IANA-namn, i ett sparat agentsvar eller ett infogat svar, så anger agenten datum
och tid samt erbjuder tider i den zonen för det här samtalet, före agentens egen
tidszon:

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

Skicka zonen i stället för en tidsstämpel i `variables`: agentens klocka fortsätter
att gå under samtalet, och en tid du skickar in blir inaktuell. Ett värde som inte
är en IANA-zon ignoreras och samtalet kopplas upp i agentens zon.

## Mönster

### Kontext för inloggad användare

I widgetar i webhookläge vet besökarens sida redan vem de
är. Anropa din webhook med en frågesträngsparameter som widget-SDK:n
vidarebefordrar (`?customer_id=123`) och slå upp kunden på serversidan.

### A/B-lansering av promptar

Innan du bygger detta själv bör du notera att ThunderPhone har en inbyggd
funktion för [Experiment](/sv/guides/concepts)
(`/dashboard/experiments` och fliken **A/B** i agentbyggaren) som
definierar varianter, delar upp trafiken och jämför resultat per variant —
ingen webhook krävs.

Om du ändå behöver kontroll på webhook-sidan: hasha `call_id` → bucket;
servera prompt A för `0..49` och prompt B för `50..99`. Registrera vilken
bucket du valde i din egen databas och korrelera den senare med det
slutförda samtalets betyg.

### Tidsbaserad routing

Öppettider → agent för "live-support"; utanför öppettider → agent för "ta ett meddelande".
Enbart växling på `new Date().getUTCHours()` i din hanterare.

---

## Nästa steg

<CardGroup cols={2}>
  <Card title="Referens för webhook för inkommande samtal" icon="phone" href="/sv/webhooks/call-incoming">
    Exakta scheman för förfrågan och svar, inklusive alla konfigurationsnycklar.
  </Card>
  <Card title="Verifiera webhooksignaturer" icon="shield-check" href="/sv/guides/verify-webhook-signatures">
    Implementera HMAC korrekt en gång; återanvänd överallt.
  </Card>
  <Card title="Bygg en verktygsintegration" icon="screwdriver-wrench" href="/sv/guides/build-tool-integration">
    Kombinera dynamisk routing med verktyg per agent.
  </Card>
  <Card title="Leveranssemantik" icon="bolt" href="/sv/webhooks/overview">
    Försök igen, ordning, tidsgränser.
  </Card>
</CardGroup>
