---
title: "Configurare dinamică pentru fiecare apel"
description: "Alegeți agentul care răspunde — sau rescrieți-i promptul și setările — separat pentru fiecare apel primit, pe baza unei logici personalizate dintr-un webhook pe care îl controlați."
---

În mod implicit, fiecărui număr de telefon și fiecărei chei publicabile îi este atribuit un agent static. Când aveți nevoie de personalizare **pentru fiecare apelant** sau **pentru fiecare vizitator** — rutare VIP, context pentru utilizatorii autentificați, teste A/B pentru prompturi — treceți la modul webhook și lăsați serverul dumneavoastră să decidă.

## Cum funcționează

1. Vă abonați la evenimentul [`telephony.incoming`](/ro/webhooks/events)
   (telefon) sau [`web.incoming`](/ro/webhooks/events) (widget).
   Ambele sunt webhookuri **blocante**: ThunderPhone așteaptă răspunsul
   dumneavoastră înainte de a continua apelul, până la 13 secunde pentru un apel
   telefonic (inclusiv o reîncercare) și 10 secunde pentru o sesiune de widget.
2. ThunderPhone vă trimite `{call_id, from_number, to_number}` (sesiunile de widget
   includ câmpuri specifice widgetului în locul numerelor — consultați
   [schema cererii](/ro/webhooks/call-incoming)).
3. Serverul dumneavoastră răspunde cu o configurație a agentului (prompt, voce,
   produs, instrumente). ThunderPhone utilizează acea configurație pentru apel.
4. Nu există agent de rezervă. Dacă returnați `{}` sau o configurație nevalidă
   sau dacă cererea eșuează în continuare după
   [reîncercare](/ro/webhooks/call-incoming), apelul telefonic
   este încheiat imediat și înregistrat cu `end_reason`
   `inbound_config_webhook_failed`; solicitarea unei sesiuni de widget eșuează.

<Note>
  Funcționează identic pentru apeluri telefonice (`telephony.incoming`) și
  sesiuni widget (`web.incoming`), indiferent dacă sunt livrate către un endpoint
  webhook sau către webhookul vechi cu URL unic.
</Note>

<Warning>
  **Nu este redat niciun anunț de consimțământ ThunderPhone pentru apelurile
  configurate inline prin webhook.** Această cale ocolește anunțul de început
  de apel la nivel de agent și este exclusă în mod expres din cadrul
  ThunderPhone pentru anunțurile de consimțământ (Termenii serviciului, secțiunea
  „Înregistrare și consimțământ”). Organizația dumneavoastră este responsabilă
  pentru toate notificările și consimțămintele privind înregistrarea, monitorizarea,
  participarea AI și identificarea apelantului pentru aceste apeluri. Includeți-le
  în propriul flux — de exemplu, în scriptul de deschidere al promptului —
  înainte de a activa modul webhook inline. Un răspuns webhook care face referire la un
  `agent_id` salvat utilizează politica normală de înregistrare și informare a acelui agent.
</Warning>

## 1. Configurați destinația webhookului

<Tabs>
<Tab title="Apeluri telefonice">
Pentru numere de telefon, abonați endpointul dumneavoastră la `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"]
  }'
```

Răspunsul include un `secret` cu utilizare unică — salvați-l; îl veți utiliza
pentru verificarea semnăturii.
</Tab>
<Tab title="Widget web">
Pentru sesiunile widget, creați o cheie publicabilă în `mode="webhook"`
cu URL-ul endpointului dumneavoastră inclus:

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

Widgetul va trimite o cerere POST la acest URL la începutul fiecărei sesiuni.
</Tab>
</Tabs>

## 2. Implementați handlerul

Trei reguli practice:

- **Verificați semnătura** la fiecare solicitare (consultați
  [Verificarea semnăturilor webhook](/ro/guides/verify-webhook-signatures)).
  Nu săriți peste acest pas în dezvoltare — faceți-l corect o dată și reutilizați-l.
- **Răspundeți rapid**. Prima încercare a unui apel telefonic este întreruptă după 10
  secunde, iar întregul schimb după 13, iar fiecare secundă înseamnă liniște
  pentru apelant. Efectuați căutări în baza de date dacă este necesar, dar nu apelați sincron
  LLM-uri din etapele ulterioare — dacă doriți generarea dinamică a prompturilor,
  precalculați și stocați în cache.
- **Reveniți curat la valoarea implicită**. Pentru orice stare neașteptată, răspundeți cu un
  `prompt` și o `voice` implicite (sau cu un `agent_id` salvat). Returnarea lui `{}`
  sau a unei stări de eroare încheie apelul.

<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. Schema de răspuns

Corpul răspunsului corespunde exact
[schemei de răspuns pentru apelurile primite](/ro/webhooks/call-incoming).
Câmpurile utilizate frecvent:

| Câmp | Tip | Descriere |
|-------|------|-------------|
| `prompt` | șir (obligatoriu) | Prompt de sistem pentru agent |
| `voice` | șir (obligatoriu) | ID-ul vocii din [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | șir | Valoarea implicită este `spark`. Produsele experimentale sunt disponibile numai pentru organizațiile incluse pe lista de autorizare. |
| `background_track` | șir \| null | ID audio ambiental |
| `acknowledgement_prompt_mode` | șir | `auto` sau `manual` (numai pentru Storm cu confirmare) |
| `acknowledgement_prompt` | șir | Obligatoriu când modul este `manual` |
| `language_switching` | șir | Pentru agenți multilingvi, `ask_first` sau `follow_caller` |
| `tools` | matrice | Scheme inline pentru instrumente de funcții — consultați [Instrumente de funcții](/ro/tools/overview) |

<Note>
  Ordinea de vorbire per apel și `max_hold_seconds` nu sunt disponibile în
  răspunsul webhook. Setați-le pentru
  [agentul](/api-reference/agents) pe care îl indicați.
</Note>

### Păstrați un agent salvat și furnizați variabile

Returnați `{"agent_id": 12, "variables": {"name": "Ada"}}` pentru a utiliza agentul salvat al organizației respective
cu date specifice fiecărui apel. Promptul său poate conține `{{name}}` sau
`{{name|Friend}}`. Variabilele webhook se îmbină peste variabilele de la nivelul cererii; valoarea null
utilizează valoarea implicită a substituentului sau text gol dacă nu este furnizată nicio valoare. Valorile finale
și numele nerezolvate apar în detaliile apelului și în webhookurile de finalizare.
Răspunsurile pentru agenții salvați acceptă numai `agent_id`, `variables` și `timezone`. Dacă `prompt` este
prezent, răspunsul utilizează configurația inline și ignoră `agent_id` (inclusiv
metadatele null sau care nu sunt numere întregi); promptul inline trebuie totuși să fie valid. Răspunsurile cu configurație
inline pot include și `variables`. Răspunsurile pentru agenții salvați utilizează împărțirea A/B implementată a agentului atât pentru apelurile telefonice, cât și pentru cele din widget, apoi redau variabilele. Consultați [variabilele apelului](/ro/guides/call-variables)
pentru limite și suport pentru API-ul de sesiune. Configurația de blocare provine din URL-ul moștenit al
numărului de telefon/organizației sau dintr-o cheie de widget în modul webhook; evenimentele primite de sistemul endpoint sunt doar notificări.

### Setați fusul orar pentru fiecare apel

Un agent poate răspunde contactelor din fusuri orare diferite. Adăugați `timezone`, un
nume IANA, la un răspuns cu agent salvat sau inclus și agentul indică data
și ora și oferă intervale orare în acel fus pentru acest apel, înaintea fusului
orar propriu al agentului:

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

Trimiteți fusul orar, nu un marcaj temporal, în `variables`: ceasul agentului continuă
să ruleze în timpul apelului, iar o oră transmisă devine depășită. O valoare care nu
este un fus orar IANA este ignorată, iar apelul se conectează în fusul orar al agentului.

## Modele

### Contextul utilizatorului autentificat

În widgeturile în modul webhook, pagina vizitatorului știe deja cine este
acesta. Apelați webhookul cu un parametru din șirul de interogare pe care SDK-ul widgetului
îl redirecționează (`?customer_id=123`) și căutați clientul pe partea de server.

### Lansare graduală A/B a prompturilor

Înainte de a implementa acest lucru manual, rețineți că ThunderPhone are o funcție nativă
[Experimente](/ro/guides/concepts)
(`/dashboard/experiments` și fila **A/B** din generatorul de agenți) care
definește variante, distribuie traficul și compară rezultatele pentru fiecare variantă —
nu este necesar niciun webhook.

Dacă aveți nevoie totuși de control pe partea webhookului: aplicați hash la `call_id` → grup;
serviți promptul A pentru `0..49` și promptul B pentru `50..99`. Înregistrați grupul
ales în propria bază de date și corelați-l ulterior cu evaluarea apelului
finalizat.

### Rutare bazată pe timp

În timpul programului de lucru → agent de „asistență live”; în afara programului → agent de „preluare mesaje”.
Comutare simplă pe `new Date().getUTCHours()` în handlerul dumneavoastră.

---

## Pașii următori

<CardGroup cols={2}>
  <Card title="Referință webhook pentru apeluri primite" icon="phone" href="/ro/webhooks/call-incoming">
    Scheme exacte de solicitare + răspuns, inclusiv fiecare cheie de configurare.
  </Card>
  <Card title="Verificați semnăturile webhook" icon="shield-check" href="/ro/guides/verify-webhook-signatures">
    Configurați HMAC corect o dată; reutilizați-l peste tot.
  </Card>
  <Card title="Construiți o integrare de instrumente" icon="screwdriver-wrench" href="/ro/guides/build-tool-integration">
    Combinați rutarea dinamică cu instrumente specifice fiecărui agent.
  </Card>
  <Card title="Semantica livrării" icon="bolt" href="/ro/webhooks/overview">
    Reîncercări, ordonare, expirări.
  </Card>
</CardGroup>
