# Webhookovi jednostavno objašnjeni: potpis, ponavljanje, sigurnost

> Što je webhook, po čemu se razlikuje od API upita i kako postupati s potpisom, ponavljanjem i duplikatima. S curlom i Nodeom.

Quelle: https://whovisit.app/hr/blog/webhookovi-jednostavno-objasnjeni
Veröffentlicht: 2026-11-07
Herausgeber: Whovisit (ARAVILO, Innsbruck)

Webhook je poziv u suprotnom smjeru. Umjesto da Vaš sustav svakih nekoliko minuta pita neku uslugu je li se nešto dogodilo, usluga poziva adresu kod Vas čim se to dogodi. To štedi upite i brže donosi novosti. Zauzvrat morate osigurati primatelja i zaštititi ga. Ovaj članak objašnjava oboje.

## Webhook ili API upit

| | API upit (polling) | Webhook |
|---|---|---|
| Tko započinje? | Vaš sustav | Pošiljatelj |
| Kada? | Prema rasporedu, primjerice svaki sat | Čim nastupi događaj |
| Trud | Upiti i bez novosti | Potreban dostupan primatelj |
| Dobro za | Izvješća, usklađivanje, naknadno učitavanje | Obavijesti, prosljeđivanje |
| Rizik | Kašnjenje do sljedećeg upita | Poruka se gubi ako primatelj ne odgovori |

U praksi mnogi kombiniraju oboje: webhook prijavljuje događaj, a API po potrebi dohvaća pojedinosti ili zatvara rupe nakon ispada.

## Kako teče isporuka

1. Nastupi događaj, primjerice „uplata je stigla“.
2. Pošiljatelj šalje POST s JSON-om na Vašu pohranjenu adresu (HTTPS).
3. Vaš primatelj provjerava potpis, pamti poruku i brzo odgovara kodom iz raspona 2xx.
4. Pravi posao Vaš sustav obavlja nakon toga.

Korak 3 je važan. Stripe u [dokumentaciji o webhookovima](https://docs.stripe.com/webhooks) preporučuje da se odmah vrati kod 2xx prije nego što se pokrene složena logika koja bi mogla izazvati istek vremena. Tko prvo knjiži račun, a tek onda odgovara, riskira nepotrebna ponavljanja.

## Potpis: kako provjeriti podrijetlo

Vaša je adresa javno dostupna. Svatko bi joj dakle mogao slati izmišljene poruke. Zato ozbiljni pošiljatelji svaku poruku potpisuju zajedničkom tajnom. Uobičajen je HMAC sa SHA-256 preko sadržaja, često zajedno s vremenskom oznakom. [GitHub opisuje postupak](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) ovako: HMAC-SHA256 preko sadržaja, prikazan heksadecimalno, sa zaglavljem `X-Hub-Signature-256`. Usporedba se treba obaviti funkcijom s konstantnim trajanjem kako napadači iz vremena odgovora ne bi mogli ništa zaključiti.

Važne točke za Vašeg primatelja:

- **Koristiti sirovi sadržaj.** Potpis izračunajte preko primljenih bajtova, a ne preko ponovno serijaliziranog JSON-a. Već i drugačiji prijelom retka mijenja potpis.
- **Provjeriti vremensku oznaku.** Stripe koristi vremensku oznaku u potpisu i zadanu toleranciju od pet minuta kako bi spriječio napade ponavljanjem. Tolerancija 0 isključuje provjeru.
- **Tajnu držati tajnom.** Ne u kodu, ne u repozitoriju, nego u varijabli okruženja ili trezoru.

Smjernice otvorenog koda za pošiljatelje i primatelje okuplja inicijativa [Standard Webhooks](https://www.standardwebhooks.com/). Kako svaki pružatelj konkretno gradi svoj potpis, stoji u njegovoj dokumentaciji.

## Ponavljanja i dvostruka isporuka

Ako Vaš primatelj ne odgovori ili odgovori kodom pogreške, pošiljatelj pokušava ponovno. Stripe, primjerice, prema vlastitoj dokumentaciji ponavlja s rastućim razmacima do tri dana. Drugi pružatelji imaju druga pravila, neki isključuju webhook nakon previše neuspjeha. Iz toga slijedi:

- **Računajte s duplikatima.** Isti događaj može stići više puta, čak i kad Vaš primatelj radi ispravno. Stripe na to izričito upozorava.
- **Obradu učinite idempotentnom.** To znači: tko istu poruku obradi dvaput, ne stvara drugi rezultat. Pohranite oznaku događaja i preskočite poznato.
- **Ne oslanjajte se na redoslijed.** Događaji mogu stići zamijenjenim redoslijedom. U slučaju sumnje provjerite aktualno stanje putem API-ja.

## Sigurnost na obje strane

**Za primatelje:** samo HTTPS, provjeriti potpis i starost, ograničiti veličinu poruke, ne izvršavati naredbe iz sadržaja. Sadržaj tretirajte kao svaki ulaz izvana.

**Za pošiljatelje koji pozivaju adrese klijenata:** tko nudi webhookove, poziva adrese koje su unijeli drugi. To je klasičan put za Server-Side Request Forgery. Napadač unese internu adresu. Poslužitelj je poziva iz zaštićene mreže. [OWASP-ov opis](https://community.owasp.org/attacks/Server_Side_Request_Forgery) kao ciljeve navodi primjerice interne usluge i adrese metapodataka pružatelja oblaka. Smislene zaštitne mjere:

- dopustiti samo `https://`,
- adresu razriješiti pri unosu i prije svakog poziva te odbiti privatne, lokalne i rezervirane raspone,
- ne slijediti preusmjeravanja naslijepo,
- postaviti kratka vremenska ograničenja.

## Primjer: slanje probne poruke curlom

Pomoću curla i openssla možete testirati svog primatelja bez pravog pošiljatelja. Skripta šalje potpisanu poruku kakvu bi poslao pošiljatelj s vremenskom oznakom:

```bash
GEHEIMNIS="test-geheimnis"
ZEIT=$(date +%s)
INHALT='{"id":"evt_1","typ":"besuch.begonnen","firma":"beispiel-firma.example"}'
SIGNATUR="sha256=$(printf '%s.%s' "$ZEIT" "$INHALT" | openssl dgst -sha256 -hmac "$GEHEIMNIS" | sed 's/^.* //')"

curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Zeit: $ZEIT" \
  -H "X-Signatur: $SIGNATUR" \
  -d "$INHALT"
```

## Primjer: mali primatelj u Node.js-u

```js
const http = require('http')
const crypto = require('crypto')
const GEHEIMNIS = process.env.WEBHOOK_GEHEIMNIS || 'test-geheimnis'
const gesehen = new Set() // u stvarnosti: baza podataka

http.createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhook') { res.writeHead(404); return res.end() }
  const teile = []
  req.on('data', t => teile.push(t))
  req.on('end', () => {
    const roh = Buffer.concat(teile).toString('utf8')
    const zeit = req.headers['x-zeit'] || ''
    const erwartet = 'sha256=' + crypto.createHmac('sha256', GEHEIMNIS).update(zeit + '.' + roh).digest('hex')
    const erhalten = String(req.headers['x-signatur'] || '')
    const a = Buffer.from(erwartet), b = Buffer.from(erhalten)
    const echt = a.length === b.length && crypto.timingSafeEqual(a, b)
    const frisch = Math.abs(Date.now() / 1000 - Number(zeit)) < 300
    if (!echt || !frisch) { res.writeHead(401); return res.end() }
    const ereignis = JSON.parse(roh)
    if (!gesehen.has(ereignis.id)) { gesehen.add(ereignis.id); console.log('neu:', ereignis.typ) }
    res.writeHead(200); res.end('ok')
  })
}).listen(3000)
```

Primatelj provjerava potpis i starost, preskače poznate oznake i odmah odgovara. Za pogon dodatno trebate HTTPS, ograničenje veličine i red čekanja za daljnju obradu.

## Gdje su webhookovi rašireni

Platne usluge, platforme za kod i alati za obrasce javljaju događaje webhookom. To može i prepoznavanje posjetitelja: ako sustav prijavi tvrtku koja posjećuje Vašu stranicu, ona odmah završava u alatu po Vašem izboru. Kako to vodi u prodajni sustav, pokazuje [Kako posjete web stranici dovesti u CRM](https://whovisit.app/blog/website-besuche-ins-crm). Alternativu putem upita opisuje [Koje tvrtke posjećuju moju web stranicu?](https://whovisit.app/blog/welche-firmen-besuchen-meine-website). Koji podaci pritom teku, objašnjava [Kako funkcionira prepoznavanje tvrtki?](https://whovisit.app/blog/firmenerkennung-website-technik).

## Često postavljana pitanja

**Koja je razlika između webhooka i API-ja?** Kod API-ja Vaš sustav pita, kod webhooka se pošiljatelj sam javlja. Oboje se može kombinirati.

**Što se događa ako moj primatelj ispadne?** Većina pošiljatelja ponavlja isporuku tijekom određenog razdoblja. Koliko dugo i koliko često, stoji u dokumentaciji pružatelja. Za rupe biste trebali planirati usklađivanje putem API-ja.

**Je li dovoljno provjeriti adresu pošiljatelja?** Ne. Adrese pošiljatelja mogu se lažirati i mijenjaju se. Pouzdan je samo potpis s tajnom koju znate samo Vi i pošiljatelj.
