# Webhookit yksinkertaisesti: allekirjoitus, uusinta, turvallisuus

> Mikä webhook on, miten se eroaa API-kyselystä ja miten käsittelette allekirjoituksen, uusinnan ja kaksoiskappaleet. Esimerkkeinä curl ja Node.

Quelle: https://whovisit.app/fi/blog/webhookit-yksinkertaisesti-allekirjoitus
Veröffentlicht: 2026-11-07
Herausgeber: Whovisit (ARAVILO, Innsbruck)

Webhook on puhelu toiseen suuntaan. Sen sijaan, että järjestelmänne kysyisi palvelulta muutaman minuutin välein, onko jotain tapahtunut, palvelu kutsuu osoitettanne teillä heti kun se tapahtuu. Se säästää kyselyjä ja tuo uutiset nopeammin. Sen sijaan teidän on tarjottava vastaanottaja ja suojattava se. Tämä artikkeli selittää molemmat.

## Webhook vai API-kysely

| | API-kysely (polling) | Webhook |
|---|---|---|
| Kuka aloittaa? | Järjestelmänne | Lähettäjä |
| Milloin? | Aikataulun mukaan, esimerkiksi tunneittain | Heti kun tapahtuma sattuu |
| Työmäärä | Kyselyjä myös ilman uutisia | Tarvitaan tavoitettava vastaanottaja |
| Hyvä | Raportteihin, täsmäytykseen, jälkilataukseen | Ilmoituksiin, välitykseen |
| Riski | Viive seuraavaan kyselyyn | Viesti katoaa, jos vastaanottaja ei vastaa |

Käytännössä monet yhdistävät molemmat: webhook ilmoittaa tapahtuman, API hakee tarvittaessa yksityiskohdat tai täyttää aukot katkoksen jälkeen.

## Miten toimitus etenee

1. Tapahtuma sattuu, esimerkiksi ”maksu vastaanotettu”.
2. Lähettäjä lähettää POST-pyynnön JSON-sisällöllä tallennettuun osoitteeseenne (HTTPS).
3. Vastaanottajanne tarkistaa allekirjoituksen, tallentaa viestin ja vastaa nopeasti 2xx-alueen koodilla.
4. Varsinaisen työn järjestelmänne tekee sen jälkeen.

Kohta 3 on tärkeä. Stripe suosittelee [webhook-dokumentaatiossaan](https://docs.stripe.com/webhooks) palauttamaan 2xx-koodin heti, ennen kuin raskas logiikka käynnistyy ja voisi aiheuttaa aikakatkaisun. Joka kirjaa ensin laskun ja vastaa vasta sitten, riskeeraa tarpeettomia uusintoja.

## Allekirjoitus: miten alkuperä tarkistetaan

Osoitteenne on julkisesti tavoitettavissa. Kuka tahansa voisi siis lähettää sille keksittyjä viestejä. Siksi vakavasti otettavat lähettäjät allekirjoittavat jokaisen viestin yhteisellä salaisuudella. Tavallinen on HMAC SHA-256:lla sisällön yli, usein yhdessä aikaleiman kanssa. [GitHub kuvaa menetelmän](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) näin: HMAC-SHA256 sisällön yli, heksadesimaalisena, otsikolla `X-Hub-Signature-256`. Vertailu tulee tehdä vakioajalla toimivalla funktiolla, jotta hyökkääjät eivät voi johtaa mitään vastausajoista.

Tärkeitä kohtia vastaanottajallenne:

- **Käyttäkää raakasisältöä.** Laskekaa allekirjoitus vastaanotettujen tavujen yli, ei uudelleen sarjoitetun JSONin yli. Jo toinen rivinvaihto muuttaa allekirjoituksen.
- **Tarkistakaa aikaleima.** Stripe käyttää allekirjoituksessa aikaleimaa ja oletuksena viiden minuutin toleranssia toistohyökkäysten estämiseksi. Toleranssi 0 poistaa tarkistuksen käytöstä.
- **Pitäkää salaisuus salaisena.** Ei koodissa, ei repositoriossa, vaan ympäristömuuttujassa tai holvissa.

Avoimen lähdekoodin ohjeet lähettäjille ja vastaanottajille kokoaa aloite [Standard Webhooks](https://www.standardwebhooks.com/). Miten kukin tarjoaja muodostaa allekirjoituksensa käytännössä, kerrotaan sen dokumentaatiossa.

## Uusinnat ja kaksoistoimitus

Jos vastaanottajanne ei vastaa tai vastaa virhekoodilla, lähettäjä yrittää uudelleen. Stripe esimerkiksi toistaa oman dokumentaationsa mukaan kasvavin välein jopa kolmen päivän ajan. Muilla tarjoajilla on eri säännöt, jotkin poistavat webhookin käytöstä liian monen epäonnistumisen jälkeen. Siitä seuraa:

- **Varautukaa kaksoiskappaleisiin.** Sama tapahtuma voi saapua useasti, vaikka vastaanottajanne toimisi oikein. Stripe huomauttaa tästä nimenomaisesti.
- **Tehkää käsittelystä idempotentti.** Se tarkoittaa: joka käsittelee saman viestin kahdesti, ei tuota toista tulosta. Tallentakaa tapahtuman tunniste ja ohittakaa tutut.
- **Älkää luottako järjestykseen.** Tapahtumat voivat saapua sekaisin. Tarkistakaa epäselvässä tapauksessa nykyinen tila API:n kautta.

## Turvallisuus molemmilla puolilla

**Vastaanottajille:** vain HTTPS, tarkistakaa allekirjoitus ja ikä, rajoittakaa viestin koko, älkää suorittako komentoja sisällöstä. Käsitelkää sisältöä kuten mitä tahansa ulkopuolelta tulevaa syötettä.

**Lähettäjille, jotka kutsuvat asiakkaiden osoitteita:** Joka tarjoaa webhookeja, kutsuu osoitteita, jotka muut ovat kirjanneet. Se on klassinen reitti Server-Side Request Forgery -hyökkäykselle. Hyökkääjä kirjaa sisäisen osoitteen. Palvelin kutsuu sitä suojatusta verkosta käsin. [OWASPin kuvaus](https://community.owasp.org/attacks/Server_Side_Request_Forgery) mainitsee kohteiksi esimerkiksi sisäiset palvelut ja pilvitarjoajien metatietoosoitteet. Järkeviä suojatoimia:

- sallikaa vain `https://`,
- selvittäkää osoite kirjattaessa ja ennen jokaista kutsua ja hylätkää yksityiset, paikalliset ja varatut alueet,
- älkää seuratko edelleenohjauksia sokeasti,
- asettakaa lyhyet aikarajat.

## Esimerkki: testiviestin lähettäminen curlilla

Curlilla ja opensslillä voitte testata vastaanottajanne ilman oikeaa lähettäjää. Skripti lähettää allekirjoitetun viestin sellaisena kuin lähettäjä lähettäisi sen aikaleimalla:

```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"
```

## Esimerkki: pieni vastaanottaja Node.js:llä

```js
const http = require('http')
const crypto = require('crypto')
const GEHEIMNIS = process.env.WEBHOOK_GEHEIMNIS || 'test-geheimnis'
const gesehen = new Set() // oikeassa käytössä: tietokanta

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)
```

Vastaanottaja tarkistaa allekirjoituksen ja iän, ohittaa tunnetut tunnisteet ja vastaa heti. Tuotantokäyttöön tarvitsette lisäksi HTTPS:n, kokorajoituksen ja jonon jatkokäsittelyä varten.

## Missä webhookit ovat yleisiä

Maksupalvelut, koodialustat ja lomaketyökalut ilmoittavat tapahtumista webhookilla. Myös kävijätunnistus voi: kun järjestelmä ilmoittaa yrityksen, joka käy sivustollanne, se päätyy suoraan valitsemaanne työkaluun. Miten se johtaa myyntijärjestelmään, näyttää [Verkkosivukäynnit CRM:ään: 4 tapaa vertailussa](https://whovisit.app/blog/website-besuche-ins-crm). Kyselyyn perustuvan vaihtoehdon kuvaa [Mitkä yritykset käyvät verkkosivustollani? Kolme tapaa lyhyesti](https://whovisit.app/blog/welche-firmen-besuchen-meine-website). Mitä tietoja siinä liikkuu, selittää [Miten yritystunnistus toimii? Tekniikka kuudessa vaiheessa](https://whovisit.app/blog/firmenerkennung-website-technik).

## Usein kysyttyä

**Mikä ero on webhookilla ja API:lla?** API:ssa järjestelmänne kysyy, webhookissa lähettäjä ilmoittautuu itse. Molemmat voi yhdistää.

**Mitä tapahtuu, jos vastaanottajani kaatuu?** Useimmat lähettäjät toistavat toimituksen tietyn ajan. Kuinka kauan ja kuinka usein, kerrotaan tarjoajan dokumentaatiossa. Aukkoja varten kannattaa suunnitella täsmäytys API:n kautta.

**Riittääkö lähettäjän osoitteen tarkistus?** Ei. Lähettäjäosoitteita voi väärentää ja ne muuttuvat. Luotettava on vain allekirjoitus salaisuudella, jonka tuntevat vain te ja lähettäjä.
