---
title: "Webhook apžvalga"
description: "Kaip ThunderPhone pateikia įvykius realiuoju laiku, kaip patikrinti parašus ir kuo skiriasi senasis bei galinio taško pagrindu veikiantys pateikimo modeliai."
---

ThunderPhone siunčia HTTP `POST` užklausas į jūsų serverį, kai skambučio
metu įvyksta tam tikri veiksmai — prasideda įeinantis skambutis, baigiasi skambutis, užbaigiamas vertinimo
vykdymas, suveikia įspėjimas ir pan. Yra **du pristatymo
modeliai**:

<CardGroup cols={2}>
  <Card title="Žiniatinklio kabliuko galiniai taškai (rekomenduojama)" icon="bolt" href="/lt/webhooks/endpoints">
    Kelios URL, kiekvienam galiniam taškui skirti slapti raktai, kiekvienam galiniam taškui skirti įvykių filtrai
    ir automatiniai pakartotiniai bandymai.
    Valdykite naudodami `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>
  <Card title="Vienos URL senasis žiniatinklio kabliukas" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Viena URL vienai organizacijai. Perduoda skambučio ciklo įvykius, įskaitant
    **blokuojančius** konfigūracijos mainus. Valdoma per `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Visi dešimt įvykių tipų, pateiktų [įvykių kataloge](/lt/webhooks/events),
pristatomi per žiniatinklio kabliuko galinius taškus. Šeši skambučio ciklo įvykiai
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) **taip pat** siunčiami į
senąjį vienos URL žiniatinklio kabliuką — jei turite ir senąją URL, ir
atitinkamą galinį tašką, įvykį gaunate **abiejais** keliais. Blokavimo
veikimas ( [`telephony.incoming` / `web.incoming` konfigūracijos
mainai](/lt/webhooks/call-incoming) ir žiniatinklio kabliuko režimo
[įrankių iškvietimas](/lt/tools/overview)) galimas
tik senuoju keliu; kiekvienas pristatymas į galinį tašką yra
pranešimas, kuriam atsakymo nelaukiama.

## Naudingosios apkrovos formatas

Į galinius taškus pristatoma JSON objektas su `data`, `event_id` ir
`type`:

```json
{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}
```

`event_id` yra unikalus kiekvienam sugeneruotam įvykiui. Jis yra toks pats per pakartotinius bandymus
**ir** visuose galiniuose taškuose, kurie gauna įvykį — naudokite jį dublikatams pašalinti.

Senasis vienos URL žiniatinklio kabliukas siunčia tuos pačius `type` ir `data`, tačiau
**be** `event_id`:

```json
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

Perduodant tinklu, kiekvienas užklausos turinys serializuojamas kanoniškai — raktai surūšiuojami
abėcėlės tvarka, nėra tarpų, naudojamas UTF-8. Šiuose dokumentuose pateikti
skaitymui suformatuoti pavyzdžiai skirti tik aiškumui.

Visą įvykių tipų ir naudingosios apkrovos laukų sąrašą rasite
[įvykių kataloge](/lt/webhooks/events).

## Parašo patvirtinimas

Kiekvienoje užklausoje antraštėje `X-ThunderPhone-Signature` pateikiamas HMAC-SHA256 parašas, apskaičiuotas pagal **neapdorotą užklausos
turinį**. Pasirašymo raktas yra galinio taško `secret` (arba organizacijos lygio webhook `secret`, skirtas senesniems
pristatymams).

### Veiksmai

1. Perskaitykite neapdorotą užklausos turinį **prieš** bet kokį analizavimą.
2. Apskaičiuokite `hmac_sha256(secret, body).hexdigest()`.
3. Pastoviuoju laiku palyginkite su antrašte `X-ThunderPhone-Signature`.

Pasirašome tiksliai tuos baitus, kuriuos perduodame, o tie baitai yra
kanoninis JSON serializavimas (surikiuoti raktai, kompaktiški skirtukai). Todėl
patvirtinimas pagal neapdorotą turinį visada veikia — o jei jūsų sistema
pateikia tik išanalizuotą JSON, pakartotinis jo serializavimas su surikiuotais raktais ir
kompaktiškais skirtukais sukuria identiškus baitus. Abu būdai aprašyti
[patvirtinimo vadove](/lt/guides/verify-webhook-signatures).

<CodeGroup>
```python Python
import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature or "")

# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)

@app.post("/thunderphone-webhook")
def handle():
    body = request.get_data()
    sig = request.headers.get("X-ThunderPhone-Signature", "")
    if not verify_signature(body, sig, WEBHOOK_SECRET):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
```

```javascript Node.js (Express)
import crypto from "node:crypto";
import express from "express";

function verifySignature(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  if (!signature || expected.length !== signature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}

const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-ThunderPhone-Signature") || "";
    if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);
```
</CodeGroup>

## Pristatymo semantika

Ši semantika taikoma **galinio punkto** pristatymams. Senasis vieno URL
žiniatinklio kabliukas yra vienas sinchroninis bandymas be pakartotinių bandymų.

<AccordionGroup>
  <Accordion title="Pakartotiniai bandymai">
    Kiekvienas įvykis iškart bandomas pristatyti vieną kartą. Bet koks `2xx` atsakymas
    patvirtina pristatymą. Esant bet kokiai kitai baigčiai (ne 2xx,
    ryšio klaidai, laiko limitui), bandome dar kartą **po 1 min., 5 min., 30 min., 2 val., 6 val.,
    12 val. ir 24 val. nuo pirmojo bandymo** — iš viso 8 bandymai per
    24 valandas. Jei nepavyksta visiems bandymams, pristatymas sustabdomas, o galinis punktas
    [žiniatinklio kabliukų galiniuose punktuose](/lt/webhooks/endpoints) pažymimas būsena
    `status="failing"`. Grąžinkite `2xx` iškart, kai naudingasis krovinys
    patikimai priimamas; apdorokite jį asinchroniškai.
  </Accordion>

  <Accordion title="Eiliškumas">
    Pristatymo eiliškumas užtikrinamas dedant visas pastangas. Praktikoje pristatome tokia
    tvarka, kuria įvykiai sugeneruojami, tačiau nepavykus bandymams pakartotiniai bandymai gali pakeisti eilę.
    Visada šalinkite dublikatus ir sutaikykite pagal `call_id` / objekto ID.
  </Accordion>

  <Accordion title="Dublikatai">
    Pristatymas yra **bent kartą**: pakartotinis bandymas po atsakymo, kurio negavome,
    gali dubliuoti įvykį. Kiekvienas pakartotinis bandymas turi tą patį
    `event_id`, todėl saugokite apdorotus ID ir praleiskite pasikartojimus. `event_id` taip pat
    bendrinamas tarp galinių punktų — du galiniai punktai, užsiprenumeravę tą
    patį įvykį, gauna tą patį `event_id`.
  </Accordion>

  <Accordion title="Laiko limitai">
    Galinių punktų pristatymams taikomas **30 s** laiko limitas kiekvienam bandymui. Senajame
    kelyje blokuojančios užklausos, lemiančios tiesioginio skambučio veikimą —
    [`telephony.incoming` / `web.incoming`](/lt/webhooks/call-incoming)
    konfigūracijos apsikeitimas — baigia galioti po **10 s**, tačiau lėtas
    atsakymas vėlina skambučio priėmimą, todėl siekite atsakyti per kelias
    sekundes. Žiniatinklio kabliuko režimo [įrankių iškvietimas](/lt/tools/overview) pagal numatytuosius nustatymus leidžia 20 s,
    o įrankių deklaracijose galima nustatyti aukščiausio lygio `timeout`.
  </Accordion>

  <Accordion title="Šaltinio IP adresai">
    Siunčiami žiniatinklio kabliukai gaunami iš ThunderPhone debesijos IP adresų diapazono.
    Jei jūsų užkarda reikalauja leidžiamųjų sąrašo, susisiekite su palaikymo komanda ir pateiksime
    aktualius diapazonus.
  </Accordion>
</AccordionGroup>

## Pasirinkimas tarp senųjų ir galinių punktų pagrindu veikiančių žiniatinklio kabliukų

| Funkcija | Senasis (`/v1/webhook`) | Galiniai punktai (`/v1/developer/webhook-endpoints`) |
|---------|------------------------|----------------------------------------------|
| URL skaičius | 1 vienai organizacijai | Keli vienai organizacijai |
| Įvykių aprėptis | Tik `telephony.*` / `web.*` | Visi 10 įvykių tipų |
| Įvykių filtras | — | Kiekvienam galiniam punktui |
| Pakartotiniai bandymai | Nėra | 8 bandymai per 24 val. |
| Vokas | `type` + `data` | `type` + `data` + `event_id` |
| Slaptojo rakto keitimas | Pakeičia vieną slaptąjį raktą | Slaptasis raktas kiekvienam galiniam punktui |
| Išjungimas neištrinant | `PUT /v1/webhook` su `{"url": ""}` | `status=disabled` |
| Būsenos matomumas | — | `active` / `disabled` / `failing` |
| Blokuojantis konfigūracijos apsikeitimas | Taip ([`telephony.incoming` / `web.incoming`](/lt/webhooks/call-incoming)) | Niekada — tik pranešimai |
| Geriausia naudoti | Dinaminei skambučių konfigūracijai | Įvykių vartojimui produkcinėje aplinkoje |

Naujose integracijose įvykius reikėtų vartoti per galinių punktų pagrindu
veikiančius žiniatinklio kabliukus. Senąjį URL palikite (arba pridėkite) tik jei
dinamiškai konfigūruojate skambučius jų priėmimo metu arba naudojate žiniatinklio kabliuko režimo įrankių iškvietimą — šie
užklausos ir atsakymo apsikeitimai vykdomi tik senuoju keliu.

---

## Susiję

<CardGroup cols={2}>
  <Card title="Įvykių katalogas" icon="list" href="/lt/webhooks/events">
    Visi įvykių tipai ir jų naudingi kroviniai.
  </Card>
  <Card title="Žiniatinklio kabliukų galiniai punktai" icon="bolt" href="/lt/webhooks/endpoints">
    Tvarkykite kelis galinius punktus, įvykių filtrus ir slaptuosius raktus.
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/lt/webhooks/call-incoming">
    Blokuojanti užklausa, į kurią jūsų serveris turi atsakyti, kad sukonfigūruotų skambučius.
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/lt/webhooks/call-complete">
    Po skambučio pateikiamas naudingasis krovinys su transkriptu, įrašu ir metrika.
  </Card>
</CardGroup>
