---
title: "Pregled web-dojavnika"
description: "Kako ThunderPhone isporučuje događaje u stvarnom vremenu, kako provjeriti potpise te usporedba naslijeđenog modela isporuke i modela isporuke temeljenog na krajnjim točkama."
---

ThunderPhone šalje HTTP `POST` zahtjeve vašem poslužitelju kada se tijekom poziva nešto dogodi — započne dolazni poziv, poziv završi, dovrši se pokretanje ocjenjivanja, aktivira se upozorenje i slično. Postoje **dva modela isporuke**:

<CardGroup cols={2}>
  <Card title="Krajnje točke webhooka (preporučeno)" icon="bolt" href="/hr/webhooks/endpoints">
    Više URL-ova, tajne po krajnjoj točki, filtri događaja po krajnjoj točki
    i automatski ponovni pokušaji.
    Upravljajte putem `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>
  <Card title="Naslijeđeni webhook s jednim URL-om" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Jedan URL po organizaciji. Prenosi događaje životnog ciklusa poziva, uključujući
    **blokirajuće** razmjene konfiguracije. Njime se upravlja putem `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Svih deset vrsta događaja iz [kataloga događaja](/hr/webhooks/events)
isporučuje se putem krajnjih točaka webhooka. Šest događaja životnog ciklusa poziva
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) **također** se šalje na
naslijeđeni webhook s jednim URL-om — ako imate i naslijeđeni URL i
odgovarajuću krajnju točku, događaj primate putem **obje** putanje. Blokirajuće
ponašanje ([razmjena konfiguracije za `telephony.incoming` / `web.incoming`](/hr/webhooks/call-incoming)
i [slanje alata](/hr/tools/overview) u načinu rada webhooka)
postoji isključivo na naslijeđenoj putanji; svaka isporuka krajnjoj točki
obavijest je bez čekanja odgovora.

## Format podataka

Isporuke krajnjoj točki JSON su objekt s poljima `data`, `event_id` i
`type`:

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

`event_id` jedinstven je za svaki emitirani događaj. Isti je pri ponovnim pokušajima
**i** na svakoj krajnjoj točki koja primi događaj — upotrijebite ga za uklanjanje duplikata.

Naslijeđeni webhook s jednim URL-om šalje isti `type` i `data`, ali
**bez** polja `event_id`:

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

Pri prijenosu se svako tijelo serijalizira kanonski — ključevi su poredani
abecedno, bez razmaka, u UTF-8 kodiranju. Primjeri s oblikovanim prikazom u
ovoj dokumentaciji služe samo za čitljivost.

Pogledajte [Katalog događaja](/hr/webhooks/events) za potpuni popis vrsta događaja
i polja podataka.

## Provjera potpisa

Svaki zahtjev nosi HMAC-SHA256 potpis nad **izvornim tijelom
zahtjeva** u zaglavlju `X-ThunderPhone-Signature`. Ključ za potpisivanje je
`secret` krajnje točke (ili `secret` vašeg webhooka na razini organizacije za
naslijeđene isporuke).

### Koraci

1. Pročitajte izvorno tijelo zahtjeva **prije** bilo kakvog parsiranja.
2. Izračunajte `hmac_sha256(secret, body).hexdigest()`.
3. Usporedite u konstantnom vremenu sa zaglavljem `X-ThunderPhone-Signature`.

Potpisujemo točno bajtove koje šaljemo, a ti bajtovi predstavljaju
kanonsku JSON serijalizaciju (sortirani ključevi, sažeti razdjelnici). Stoga
provjera prema izvornom tijelu uvijek funkcionira — a ako vam vaš okvir
prosljeđuje samo parsirani JSON, ponovna serijalizacija sa sortiranim ključevima i
sažetim razdjelnicima stvara identične bajtove. Obje su metode obuhvaćene u
[vodiču za provjeru](/hr/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>

## Semantika isporuke

Ova semantika primjenjuje se na isporuke na **krajnje točke**. Naslijeđeni webhook s jednim URL-om jedan je sinkroni pokušaj bez ponavljanja.

<AccordionGroup>
  <Accordion title="Ponovni pokušaji">
    Svaki se događaj pokušava isporučiti jednom odmah. Svaki odgovor `2xx`
    potvrđuje isporuku. Za svaki drugi ishod (koji nije 2xx,
    pogreška veze, vremensko ograničenje) ponavljamo pokušaj **1 min, 5 min, 30 min, 2 h, 6 h,
    12 h i 24 h nakon prvog pokušaja** — 8 pokušaja tijekom
    24 sata. Ako svaki pokušaj ne uspije, isporuka se zaustavlja, a krajnja točka
    označava se kao `status="failing"` u
    [krajnjim točkama webhooka](/hr/webhooks/endpoints). Vratite `2xx` čim je
    payload trajno prihvaćen; obradite ga asinkrono.
  </Accordion>

  <Accordion title="Redoslijed">
    Redoslijed isporuke temelji se na najboljem mogućem nastojanju. U praksi isporučujemo redoslijedom
    kojim se događaji emitiraju, ali ponovni pokušaji mogu promijeniti redoslijed nakon neuspjeha.
    Uvijek uklonite duplikate i uskladite podatke prema `call_id` / ID-u objekta.
  </Accordion>

  <Accordion title="Duplikati">
    Isporuka je **najmanje jednom**: ponovni pokušaj nakon odgovora koji nikada
    nismo primili može duplicirati događaj. Svaki ponovni pokušaj nosi isti
    `event_id`, stoga pohranite obrađene ID-ove i preskočite ponavljanja. `event_id` je
    također zajednički za krajnje točke — dvije krajnje točke pretplaćene na
    isti događaj primaju isti `event_id`.
  </Accordion>

  <Accordion title="Vremenska ograničenja">
    Isporuke na krajnje točke imaju vremensko ograničenje od **30 s** po pokušaju. Na
    naslijeđenoj putanji, blokirajući zahtjevi koji upravljaju ponašanjem aktivnog poziva —
    razmjena konfiguracije [`telephony.incoming` / `web.incoming`](/hr/webhooks/call-incoming) —
    istječu nakon **10 s**, ali spor odgovor odgađa javljanje na poziv, stoga
    odgovorite u roku od nekoliko sekundi. Slanje alata u načinu webhooka
    [slanje alata](/hr/tools/overview) prema zadanim postavkama dopušta 20 s,
    a deklaracije alata mogu postaviti `timeout` na najvišoj razini.
  </Accordion>

  <Accordion title="Izvorne IP adrese">
    Odlazni webhookovi dolaze iz raspona IP adresa ThunderPhone clouda.
    Ako vaš vatrozid zahtijeva popis dopuštenih adresa, obratite se podršci i podijelit ćemo
    trenutačne raspone.
  </Accordion>
</AccordionGroup>

## Odabir između naslijeđenih webhookova i webhookova temeljenih na krajnjim točkama

| Značajka | Naslijeđeni (`/v1/webhook`) | Krajnje točke (`/v1/developer/webhook-endpoints`) |
|---------|------------------------|----------------------------------------------|
| Broj URL-ova | 1 po organizaciji | Više po organizaciji |
| Obuhvat događaja | Samo `telephony.*` / `web.*` | Svih 10 vrsta događaja |
| Filtar događaja | — | Po krajnjoj točki |
| Ponovni pokušaji | Nema | 8 pokušaja tijekom 24 h |
| Omotnica | `type` + `data` | `type` + `data` + `event_id` |
| Rotacija tajne | Zamjenjuje jednu tajnu | Tajna po krajnjoj točki |
| Onemogućavanje bez brisanja | `PUT /v1/webhook` s `{"url": ""}` | `status=disabled` |
| Vidljivost statusa | — | `active` / `disabled` / `failing` |
| Blokirajuća razmjena konfiguracije | Da ([`telephony.incoming` / `web.incoming`](/hr/webhooks/call-incoming)) | Nikada — samo obavijesti |
| Najbolje za | Dinamičku konfiguraciju poziva | Korištenje događaja u produkciji |

Nove integracije trebaju primati događaje putem webhookova temeljenih na
krajnjim točkama. Zadržite (ili dodajte) naslijeđeni URL samo ako dinamički konfigurirate pozive
u trenutku javljanja ili koristite slanje alata u načinu webhooka — te
razmjene zahtjeva i odgovora rade samo na naslijeđenoj putanji.

---

## Povezano

<CardGroup cols={2}>
  <Card title="Katalog događaja" icon="list" href="/hr/webhooks/events">
    Sve vrste događaja i njihovi payloadovi.
  </Card>
  <Card title="Krajnje točke webhooka" icon="bolt" href="/hr/webhooks/endpoints">
    Upravljajte višestrukim krajnjim točkama, filtrima događaja i tajnama.
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/hr/webhooks/call-incoming">
    Blokirajući zahtjev na koji vaš poslužitelj mora odgovoriti kako bi konfigurirao pozive.
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/hr/webhooks/call-complete">
    Payload nakon poziva s transkriptom, snimkom i metrikama.
  </Card>
</CardGroup>
