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

# Configurare dinamică pentru fiecare apel

> Alegeți un agent — sau rescrieți un prompt — pentru fiecare apel primit, pe baza unei logici personalizate dintr-un webhook.

Î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 conectați, teste A/B pentru prompturi — treceți la modul webhook și lăsați serverul 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ă până la
   10 secunde răspunsul dumneavoastră înainte de a continua apelul.
2. ThunderPhone vă trimite `{call_id, from_number, to_number}` (sesiunile
   widget conțin câmpuri specifice widgetului în locul numerelor — consultați
   [schema cererii](/ro/webhooks/call-incoming)).
3. Serverul dumneavoastră răspunde cu o configurație de agent (prompt, voce,
   produs, instrumente). ThunderPhone utilizează această configurație pentru apel.
4. Dacă returnați `{}`, expiră timpul de răspuns sau apare o eroare, agentul
   atribuit static este utilizat ca rezervă. O valoare implicită sigură.

<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 un singur URL.
</Note>

## 1. Configurați destinația webhookului

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

    Răspunsul include un `secret` utilizabil o singură dată — salvați-l; îl veți utiliza
    pentru verificarea semnăturii.
  </Tab>

  <Tab title="Widget web">
    Pentru sesiuni widget, creați o cheie publicabilă în `mode="webhook"`
    cu URL-ul endpointului inclus:

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

    Widgetul va trimite un POST către acest URL la începutul fiecărei sesiuni.
  </Tab>
</Tabs>

## 2. Implementați handlerul

Trei reguli de bază:

* **Verificați semnătura** pentru fiecare solicitare (consultați
  [Verificarea semnăturilor webhook](/ro/guides/verify-webhook-signatures)).
  Nu omiteți acest pas în dezvoltare — implementați-l corect o dată și reutilizați-l.
* **Răspundeți rapid**. Zece secunde reprezintă limita strictă, iar fiecare secundă înseamnă
  tăcere pentru apelant. Efectuați căutări în baza de date dacă este necesar, dar
  nu apelați sincron LLM-uri din aval — dacă doriți generare dinamică de prompturi,
  precalculați și stocați în cache.
* **Folosiți un fallback curat**. Orice stare neașteptată trebuie să returneze `{}` pentru ca
  agentul alocat static să gestioneze apelul.

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

Corpul răspunsului corespunde exact
[schemei de răspuns pentru apeluri 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 vocal din [`GET /v1/voices`](/api-reference/agents#voices)                                         |
| `product`                     | șir               | Valoarea implicită este `spark`                                                                       |
| `background_track`            | șir \| null       | ID audio ambiental                                                                                    |
| `acknowledgement_prompt_mode` | șir               | `auto` sau `manual` (numai Storm-with-ack)                                                            |
| `acknowledgement_prompt`      | șir               | Obligatoriu când modul este `manual`                                                                  |
| `tools`                       | matrice           | Scheme inline pentru instrumente de funcție — consultați [Instrumente de funcție](/ro/tools/overview) |

<Note>
  Ordinea de vorbire per apel și `max_hold_seconds` nu sunt disponibile în
  răspunsul webhook. Configurați-le pe
  [Agent](/api-reference/agents) la care faceți referire.
</Note>

## Modele

### Contextul utilizatorului autentificat

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

### Lansare graduală A/B a prompturilor

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

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

### Rutare bazată pe timp

Program de lucru → agent de „asistență în direct”; în afara programului → agent de „preluare a unui mesaj”.
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 pentru cereri și răspunsuri, inclusiv fiecare cheie de configurare.
  </Card>

  <Card title="Verificați semnăturile webhookurilor" icon="shield-check" href="/ro/guides/verify-webhook-signatures">
    Configurați HMAC corect o dată; reutilizați-l peste tot.
  </Card>

  <Card title="Creați o integrare de instrumente" icon="screwdriver-wrench" href="/ro/guides/build-tool-integration">
    Combinați rutarea dinamică cu instrumente per agent.
  </Card>

  <Card title="Semantica livrării" icon="bolt" href="/ro/webhooks/overview">
    Reîncercări, ordonare, expirări.
  </Card>
</CardGroup>
