Siirry sisältöön

Tekniikka · 7. marraskuuta 2026 · Lukuaika 5 min

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.

Kaksi tietä vertailussa: API-kyselyssä järjestelmänne kysyy yhä uudelleen, webhookissa lähettäjä kutsuu osoitettanne kerran heti kun jotain tapahtuu
Kaavio: Whovisit · Katso kaavio suurena

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änneLähettäjä
Milloin?Aikataulun mukaan, esimerkiksi tunneittainHeti kun tapahtuma sattuu
TyömääräKyselyjä myös ilman uutisiaTarvitaan tavoitettava vastaanottaja
HyväRaportteihin, täsmäytykseen, jälkilataukseenIlmoituksiin, välitykseen
RiskiViive seuraavaan kyselyynViesti 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 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 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. 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 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:

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ä

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. Kyselyyn perustuvan vaihtoehdon kuvaa Mitkä yritykset käyvät verkkosivustollani? Kolme tapaa lyhyesti. Mitä tietoja siinä liikkuu, selittää Miten yritystunnistus toimii? Tekniikka kuudessa vaiheessa.

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

Lue lisää