---
title: "Dynaaminen puhelukohtainen määritys"
description: "Valitse vastaava agentti — tai kirjoita sen kehote ja asetukset uudelleen — erikseen jokaiselle saapuvalle puhelulle itse hallinnoimasi webhookin mukautetun logiikan perusteella."
---

Oletusarvoisesti jokaiselle puhelinnumerolle ja julkaistavalle avaimelle
on määritetty staattinen agentti. Kun tarvitset **soittajakohtaista** tai
**vierailijakohtaista** mukautusta — VIP-reititystä, sisäänkirjautuneen käyttäjän kontekstia,
A/B-kehotetestejä — vaihda webhook-tilaan ja anna palvelimesi päättää.

## Näin se toimii

1. Tilaat [`telephony.incoming`](/fi/webhooks/events)-
   (puhelin) tai [`web.incoming`](/fi/webhooks/events)-
   (widget) tapahtuman. Molemmat ovat **estävät** webhookit: ThunderPhone odottaa
   vastaustasi ennen puhelun jatkamista, puhelussa enintään 13 sekuntia
   (sisältää yhden uudelleenyrityksen) ja widget-istunnossa 10 sekuntia.
2. ThunderPhone lähettää sinulle `{call_id, from_number, to_number}` (widget-
   istunnot sisältävät numeroiden sijaan widget-kohtaisia kenttiä — katso
   [pyynnön skeema](/fi/webhooks/call-incoming)).
3. Palvelimesi vastaa agentin määrityksellä (kehote, ääni,
   tuote, työkalut). ThunderPhone käyttää tätä määritystä puhelussa.
4. Varapuheagenttia ei ole. Jos palautat `{}` tai virheellisen
   määrityksen tai pyyntö epäonnistuu edelleen
   [uudelleenyrityksen](/fi/webhooks/call-incoming) jälkeen, puhelu
   päätetään välittömästi ja kirjataan `end_reason`-
   arvolla `inbound_config_webhook_failed`; widget-istunnon pyyntö epäonnistuu.

<Note>
  Toimii samalla tavalla puheluissa (`telephony.incoming`) ja widget-
  istunnoissa (`web.incoming`) riippumatta siitä, toimitetaanko ne webhook-
  päätepisteeseen vai vanhaan yhden URL-osoitteen webhookiin.
</Note>

<Warning>
  **ThunderPhonen suostumusilmoitusta ei toisteta inline-webhookilla määritetyissä
  puheluissa.** Tämä polku ohittaa agenttitason puhelun aloitusilmoituksen
  ja on nimenomaisesti rajattu ThunderPhonen suostumusilmoituskehyksen
  (käyttöehdot, "Tallennus ja suostumus" -osio) ulkopuolelle. Organisaatiosi
  vastaa kaikista näiden puheluiden tallennusta, seurantaa, tekoälyn osallistumista
  ja soittajan tunnistamista koskevista ilmoituksista ja suostumuksista. Sisällytä
  ne omaan kulkuusi — esimerkiksi kehotteen aloitusskriptiin —
  ennen inline-webhook-tilan käyttöönottoa. Webhook-vastaus, joka viittaa
  tallennettuun `agent_id`-tunnukseen, käyttää kyseisen agentin normaalia
  tallennus- ja ilmoituskäytäntöä.
</Warning>

## 1. Määritä webhook-kohde

<Tabs>
<Tab title="Puhelut">
Tilaa puhelinnumeroita varten päätepisteesi `telephony.incoming`-tapahtumaan:

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

Vastaus sisältää kertaluonteisen `secret`-arvon — tallenna se; käytät sitä
allekirjoituksen varmentamiseen.
</Tab>
<Tab title="Web-widget">
Luo widget-istuntoja varten julkaistava avain tilassa `mode="webhook"`,
johon päätepisteesi URL-osoite on sisällytetty:

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

Widget lähettää POST-pyynnön tähän URL-osoitteeseen jokaisen istunnon alkaessa.
</Tab>
</Tabs>

## 2. Toteuta käsittelijä

Kolme nyrkkisääntöä:

- **Vahvista allekirjoitus** jokaisessa pyynnössä (katso
  [Webhook-allekirjoitusten vahvistaminen](/fi/guides/verify-webhook-signatures)).
  Älä ohita tätä kehityksessä — tee se oikein kerran ja käytä uudelleen.
- **Vastaa nopeasti**. Puhelun ensimmäinen yritys katkaistaan 10
  sekunnin kohdalla ja koko vuorovaikutus 13 sekunnin kohdalla, ja jokainen sekunti on soittajalle hiljaisuutta.
  Tee tarvittaessa tietokantahakuja, mutta älä kutsu jatkoketjun LLM:iä synkronisesti — jos haluat dynaamista kehotteen
  luontia, laske se etukäteen ja tallenna välimuistiin.
- **Käytä selkeää vararatkaisua**. Vastaa kaikissa odottamattomissa tilanteissa
  oletusarvoisilla `prompt`- ja `voice`-arvoilla (tai tallennetulla `agent_id`-arvolla). Palautus `{}`
  tai virhetila lopettaa puhelun.

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

Vastausrunko vastaa täsmälleen
[saapuvan puhelun vastausskeemaa](/fi/webhooks/call-incoming).
Yleisesti käytetyt kentät:

| Kenttä | Tyyppi | Kuvaus |
|-------|------|-------------|
| `prompt` | merkkijono (pakollinen) | Agentin järjestelmäkehote |
| `voice` | merkkijono (pakollinen) | Äänitunnus kohteesta [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | merkkijono | Oletusarvo on `spark`. Kokeelliset tuotteet ovat käytettävissä vain sallittujen organisaatioiden käyttöön. |
| `background_track` | merkkijono \| null | Taustaäänen tunnus |
| `acknowledgement_prompt_mode` | merkkijono | `auto` tai `manual` (vain Storm-with-ack) |
| `acknowledgement_prompt` | merkkijono | Pakollinen, kun tilana on `manual` |
| `language_switching` | merkkijono | Monikielisille agenteille: `ask_first` tai `follow_caller` |
| `tools` | taulukko | Upotetut funktiotyökalujen skeemat — katso [Funktiotyökalut](/fi/tools/overview) |

<Note>
  Puhejärjestystä ja `max_hold_seconds`-asetusta ei voi määrittää puhelukohtaisesti
  webhook-vastauksessa. Määritä ne käyttämässäsi
  [agentissa](/api-reference/agents).
</Note>

### Säilytä tallennettu agentti ja anna muuttujat

Palauta `{"agent_id": 12, "variables": {"name": "Ada"}}` käyttääksesi kyseisen organisaation
tallennettua agenttia puhelukohtaisten tietojen kanssa. Sen kehote voi sisältää `{{name}}` tai
`{{name|Friend}}`. Webhook-muuttujat yhdistetään pyyntötason muuttujien päälle; null
käyttää paikkamerkin oletusarvoa tai tyhjää tekstiä, jos oletusarvoa ei ole annettu. Lopulliset
arvot ja ratkaisemattomat nimet näkyvät puhelun tiedoissa ja valmistumisen webhookeissa.
Tallennettua agenttia käyttävät vastaukset hyväksyvät vain `agent_id`, `variables` ja `timezone`. Jos `prompt` on
mukana, vastaus käyttää rivinsisäistä määritystä ja ohittaa `agent_id`:n (mukaan lukien
null- tai ei-kokonaislukumetadatan); rivinsisäisen kehotteen on silti oltava kelvollinen. Rivinsisäistä
määritystä käyttävät vastaukset voivat sisältää myös `variables`. Tallennettua agenttia käyttävät vastaukset hyödyntävät
agentin julkaistua A/B-jakoa sekä puheluissa että widget-puheluissa ja renderöivät sitten muuttujat. Katso [puhelumuuttujat](/fi/guides/call-variables)
rajoituksia ja istunto-API-tukea varten. Estävä määritys tulee vanhasta
puhelinnumero-/organisaatio-URL-osoitteesta tai webhook-tilan widget-avaimesta; endpoint-järjestelmän
saapuvat tapahtumat ovat vain ilmoituksia.

### Aseta aikavyöhyke puhelukohtaisesti

Yksi agentti voi vastata yhteyshenkilöille eri aikavyöhykkeillä. Lisää `timezone`,
IANA-nimi, tallennetun agentin tai inline-vastauksen tietoihin, niin agentti ilmoittaa päivämäärän
ja kellonajan sekä tarjoaa tälle puhelulle aikoja kyseisellä aikavyöhykkeellä agentin
oman aikavyöhykkeen sijaan:

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

Lähetä aikavyöhyke aikaleiman sijaan kohteessa `variables`: agentin kello käy
puhelun aikana, joten välittämäsi aika vanhenee. Arvo, joka ei ole IANA-aikavyöhyke,
ohitetaan, ja puhelu yhdistetään agentin aikavyöhykkeellä.

## Mallit

### Kirjautuneen käyttäjän konteksti

Webhook-tilan widgeteissä kävijän sivu tietää jo, kuka hän
on. Kutsu webhookiasi widgetin SDK:n välittämällä kyselymerkkijonoparametrilla
(`?customer_id=123`) ja hae asiakas palvelinpuolella.

### A/B-kehotteen käyttöönotto

Ennen kuin toteutat tämän itse, huomaa, että ThunderPhone sisältää alkuperäisen
[Kokeilut](/fi/guides/concepts)-ominaisuuden
(`/dashboard/experiments` ja agenttirakentajan **A/B**-välilehti), joka
määrittää variantit, jakaa liikenteen ja vertaa tuloksia varianteittain —
webhookia ei tarvita.

Jos tarvitset silti webhook-puolen hallintaa: tiivistä `call_id` → ämpäri;
palvele kehote A arvoille `0..49` ja kehote B arvoille `50..99`. Tallenna valitsemasi
ämpäri omaan tietokantaasi ja yhdistä se myöhemmin valmistuneen
puhelun arvioon.

### Aikaperusteinen reititys

Aukioloaika → "live-tuki"-agentti; aukioloaikojen ulkopuolella → "ota viesti"
-agentti. Tee käsittelijässäsi pelkkä vaihto `new Date().getUTCHours()`-arvon perusteella.

---

## Seuraavat vaiheet

<CardGroup cols={2}>
  <Card title="Saapuvan puhelun webhook-viite" icon="phone" href="/fi/webhooks/call-incoming">
    Tarkat pyyntö- ja vastausskeemat, mukaan lukien kaikki määritysavaimet.
  </Card>
  <Card title="Vahvista webhook-allekirjoitukset" icon="shield-check" href="/fi/guides/verify-webhook-signatures">
    Tee HMAC oikein kerran ja käytä sitä kaikkialla uudelleen.
  </Card>
  <Card title="Rakenna työkalun integraatio" icon="screwdriver-wrench" href="/fi/guides/build-tool-integration">
    Yhdistä dynaaminen reititys agenttikohtaisiin työkaluihin.
  </Card>
  <Card title="Toimitussemantiikka" icon="bolt" href="/fi/webhooks/overview">
    Uudelleenyritykset, järjestys, aikakatkaisut.
  </Card>
</CardGroup>
