Webhooks

Stand: 18. August 2026

Statt den Status abzufragen, kannst du dich benachrichtigen lassen. URL und Secret hinterlegst du im Dashboard unter Einstellungen.

Nutzlast

{
  "event": "verification.completed",
  "verification_id": "3f7c…",
  "status": "SUCCESS",
  "satisfied": true,
  "profile": "age-over-18",
  "channel": "eid",
  "assurance": "high",
  "client_reference": "bestellung-4711",
  "completed_at": "2026-07-30T12:31:02.000Z"
}

status ist SUCCESS, FAILED oder EXPIREDPENDING löst nichts aus. Freigeben nur bei SUCCESS und satisfied: true.

channel sagt, womit geprüft wurde: eid für die staatliche E-ID, external für dein eigenes Verfahren (siehe Eigenes Verfahren bestätigen). assurance ist daraus abgeleitet und lässt sich nicht setzen. Beides gehört in deinen Bestellnachweis: „geprüft" allein ist keine Auskunft mehr, sobald es zwei Wege gibt.

Signatur

Jede Zustellung trägt:

valyda-signature: t=<zeitstempel>,v1=<hmac>

Signiert wird die Zeichenkette <zeitstempel>.<körper> per HMAC-SHA256 mit deinem Secret, hexadezimal ausgegeben. Das Schema entspricht dem von Stripe, weil Shop-Entwickler es kennen.

Prüfe die Signatur, bevor du den Inhalt verwendest, und vergleiche zeitkonstant. Prüfe ausserdem den Zeitstempel: liegt er mehr als fünf Minuten zurück, weise die Zustellung ab. Der Zeitstempel steckt im signierten Material, eine abgefangene Zustellung lässt sich also nicht mit frischer Zeit erneut einspielen.

import crypto from 'node:crypto'

function verify(secret, body, header) {
  const parts = Object.fromEntries(
    header.split(',').map(p => p.split('=').map(s => s.trim()))
  )
  const t = Number(parts.t)
  if (!Number.isFinite(t)) return false
  if (Math.abs(Date.now() / 1000 - t) > 300) return false

  const expected = crypto.createHmac('sha256', secret)
    .update(`${t}.${body}`).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(parts.v1 ?? '')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Signiert wird der rohe Körper. Wer erst nach JSON parst und dann wieder serialisiert, bekommt eine andere Zeichenkette und eine Signatur, die nie passt.

Zustellung

Eine Zustellung, ein Versuch, Zeitlimit acht Sekunden. Es gibt heute keine automatische Wiederholung. Antwortet dein Endpunkt mit einem Fehler oder ist er kurz nicht erreichbar, ist die Benachrichtigung verloren – der Ausgang der Prüfung bleibt in unserer Datenbank korrekt, nur weiss dein System nichts davon.

Daraus folgt: Verlass dich nicht allein auf Webhooks. Halte einen Abgleich bereit, der offene Vorgänge nach einer Weile per Statusabfrage nachzieht. Das ist auch ohne diese Einschränkung guter Stil, hier ist es notwendig.

Antworte schnell mit 2xx und erledige die Arbeit danach. Was du in der Antwortzeit tust, läuft gegen das Zeitlimit.

Mehrfache Zustellung

Behandle den Empfang idempotent – die verification_id ist der natürliche Schlüssel dafür. Auch wenn heute nur einmal zugestellt wird: eine Verarbeitung, die eine Wiederholung nicht verträgt, ist eine Verarbeitung, die dir bei der ersten Änderung dieser Zusage um die Ohren fliegt.