Webhookit
Webhookit ilmoittavat sovelluksellesi tapahtumista reaaliaikaisesti sen sijaan, että joutuisit pollaamaan tunnuspalvelua. Webhook luodaan tenantin admin-paneelissa (ks. Webhookit hallinnassa), jossa myös näet toimitushistorian ja voit uusintayrittää epäonnistuneita toimituksia.
Saatavilla olevat tapahtumat
Osio nimeltä “Saatavilla olevat tapahtumat”| Tapahtuma | Milloin laukeaa |
|---|---|
login.success |
Käyttäjä kirjautuu onnistuneesti sisään (authorization coden myöntöhetkellä) |
org.member.added |
Käyttäjä lisätään organisaatioon — suoraan tai kutsun hyväksynnän kautta |
org.member.removed |
Käyttäjä poistetaan organisaatiosta |
user.updated |
Käyttäjän sähköpostiosoite vaihtuu, joko käyttäjän omasta tilinäkymästä tai admin-paneelista. Hae tuoreet tiedot /userinfo-endpointista tai machine API:sta |
webhook.ping |
Manuaalinen testipingi admin-paneelin “Ping”-painikkeesta |
Payload-rakenne
Osio nimeltä “Payload-rakenne”Jokainen toimitus on POST-pyyntö rekisteröityyn URL-osoitteeseen.
// login.success{ "user_id": "01923f7a-...", "tenant_id": "5e3b1a2c-..." }// org.member.added{ "org_id": "5e3b1a2c-...", "user_id": "01923f7a-...", "role": "admin" }// org.member.removed{ "org_id": "5e3b1a2c-...", "user_id": "01923f7a-..." }// user.updated{ "user_id": "01923f7a-..." }// webhook.ping{ "event": "webhook.ping", "tenant_id": "5e3b1a2c-...", "endpoint_id": "8f2a...", "triggered_by": "01923f7a-..."}Headerit
Osio nimeltä “Headerit”| Header | Arvo |
|---|---|
X-Tunnuspalvelu-Event |
Tapahtumatyyppi, esim. login.success |
X-Tunnuspalvelu-Timestamp |
Unix-aika sekunteina |
X-Tunnuspalvelu-Signature |
sha256=<hex> |
Allekirjoituksen verifiointi
Osio nimeltä “Allekirjoituksen verifiointi”Kanoninen allekirjoitusmerkkijono:
{TIMESTAMP}\n{EVENT_TYPE}\n{BODY}BODY on tarkalleen lähetetty JSON-runko merkkijonona. Allekirjoitus on
HMAC-SHA256, avaimena webhookin oman, admin-paneelissa kertaalleen näytetyn
secretin.
// Node.js / TypeScriptimport crypto from "crypto";
function verifyWebhook( secret: string, timestamp: string, eventType: string, body: string, signature: string,): boolean { // Hylkää yli 5 minuuttia vanhat toimitukset const age = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)); if (age > 300) return false;
const msg = `${timestamp}\n${eventType}\n${body}`; const expected = "sha256=" + crypto.createHmac("sha256", secret).update(msg).digest("hex");
try { return crypto.timingSafeEqual( Buffer.from(expected, "utf8"), Buffer.from(signature, "utf8"), ); } catch { return false; // pituusero — ei validi vertailu }}# Pythonimport hashlib, hmac, time
def verify_webhook(secret: str, timestamp: str, event_type: str, body: str, signature: str) -> bool: age = abs(time.time() - int(timestamp)) if age > 300: return False msg = f"{timestamp}\n{event_type}\n{body}".encode() expected = "sha256=" + hmac.new( secret.encode(), msg, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature)Käytä aina ajassa vakioviivevertailua (timingSafeEqual / compare_digest),
älä tavallista ===-vertailua.
Next.js-esimerkki
Osio nimeltä “Next.js-esimerkki”import { NextRequest, NextResponse } from "next/server";
const WEBHOOK_SECRET = process.env.TUNNUSPALVELU_WEBHOOK_SECRET!;
export async function POST(req: NextRequest) { const body = await req.text(); const timestamp = req.headers.get("x-tunnuspalvelu-timestamp") ?? ""; const eventType = req.headers.get("x-tunnuspalvelu-event") ?? ""; const signature = req.headers.get("x-tunnuspalvelu-signature") ?? "";
if (!verifyWebhook(WEBHOOK_SECRET, timestamp, eventType, body, signature)) { return NextResponse.json({ error: "invalid signature" }, { status: 401 }); }
const payload = JSON.parse(body);
switch (eventType) { case "login.success": // payload.user_id, payload.tenant_id break; case "org.member.added": // payload.org_id, payload.user_id, payload.role break; case "org.member.removed": // payload.org_id, payload.user_id break; }
return NextResponse.json({ ok: true });}Toimitus ja uudelleenyritykset
Osio nimeltä “Toimitus ja uudelleenyritykset”- Toimitus on at-least-once: taustaprosessi pollaa odottavia toimituksia 5 sekunnin välein, korkeintaan 50 kerrallaan.
- Epäonnistunut toimitus (muu kuin 2xx-vastaus tai timeout) uusintayritetään eksponentiaalisella viiveellä: 60 s → 5 min → 30 min → 6 h → 24 h, korkeintaan 5 yritystä. Viidennen epäonnistuneen yrityksen jälkeen toimitus ei enää uusiudu.
- Jos endpoint vastaa toistuvasti
404:llä tai410:llä, tunnuspalvelu poistaa webhookin käytöstä automaattisesti — tarkista tila admin-paneelissa, jos toimitukset lakkaavat yllättäen saapumasta. - Endpointin URL-osoite validoidaan uudelleen jokaisella toimituskerralla (ei vain rekisteröinnin yhteydessä) DNS-uudelleenohjaushyökkäysten torjumiseksi — sisäverkkoon tai localhostiin osoittava URL hylätään.
Testaus
Osio nimeltä “Testaus”Klikkaa “Ping” webhookin admin-paneelisivulla (/admin/webhooks/{id}) —
lähettää webhook.ping-tapahtuman todelliseen toimitusjonoon, jotta voit
varmistaa allekirjoituksen tarkistuksen toimivan ilman että odotat
oikeaa käyttäjätapahtumaa.
Katso myös
Osio nimeltä “Katso myös”- Webhookit hallinnassa — endpointin luonti ja secretin hallinta admin-paneelissa
- Machine API — palvelu-palvelu-pyynnöt toiseen suuntaan