Webhook-Signaturen prüfen

Jede Zustellung ist nach dem Standard-Webhooks-Schema signiert. So prüfst du Echtheit und Aktualität in Node.js und PHP.

Mosaio signiert jede Zustellung nach dem Standard-Webhooks-Schema. Prüfe die Signatur immer, bevor du Daten verarbeitest – sonst könnte jeder deinen Endpunkt mit gefälschten Ereignissen ansprechen.

HeaderInhalt
webhook-idEindeutige ID der Zustellung – bleibt bei Wiederholungen gleich
webhook-timestampUnix-Zeit in Sekunden
webhook-signaturev1, gefolgt von der Base64-Signatur

Algorithmus

  1. Nimm das Secret (whsec_…), entferne das Präfix whsec_ und dekodiere den Rest aus Base64. Das ist der HMAC-Schlüssel.

  2. Bilde den signierten Inhalt: ${webhook-id}.${webhook-timestamp}.${roher Body}.

  3. Berechne HMAC-SHA256 und kodiere das Ergebnis als Base64.

  4. Vergleiche zeitkonstant mit dem Wert nach v1,.

  5. Lehne Zustellungen ab, deren Zeitstempel mehr als fünf Minuten abweicht.

Verwende den rohen Request-Body. Wurde er schon als JSON geparst und neu serialisiert, stimmt die Signatur nicht mehr.

Node.js

import crypto from 'node:crypto'

export function verifyMosaioWebhook(rawBody, headers, secret) {
  const id        = headers['webhook-id']
  const timestamp = headers['webhook-timestamp']
  const signature = headers['webhook-signature'] ?? ''

  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false

  const key      = Buffer.from(secret.replace(/^whsec_/, ''), 'base64')
  const expected = crypto.createHmac('sha256', key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest('base64')

  return signature.split(' ').some((part) => {
    const [version, sig] = part.split(',')
    return version === 'v1'
      && sig?.length === expected.length
      && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  })
}

PHP

function verifyMosaioWebhook(string $rawBody, array $headers, string $secret): bool {
    $id        = $headers['webhook-id'] ?? '';
    $timestamp = $headers['webhook-timestamp'] ?? '0';
    $signature = $headers['webhook-signature'] ?? '';

    if (abs(time() - (int) $timestamp) > 300) return false;

    $key      = base64_decode(preg_replace('/^whsec_/', '', $secret));
    $expected = base64_encode(hash_hmac('sha256', "$id.$timestamp.$rawBody", $key, true));

    foreach (explode(' ', $signature) as $part) {
        [$version, $sig] = array_pad(explode(',', $part, 2), 2, '');
        if ($version === 'v1' && hash_equals($expected, $sig)) return true;
    }
    return false;
}

Weil das Schema dem offenen Standard-Webhooks-Format folgt, funktionieren auch die offiziellen Standard-Webhooks-Bibliotheken für viele Sprachen.

Noch Fragen?

Unser Support antwortet in der Regel innerhalb eines Werktags.

Support kontaktieren