Zum Inhalt springen

Webhooks und Signaturprüfung

Registrieren Sie einen HTTPS-Endpunkt (Schlüssel mit Berechtigung admin); das Signaturgeheimnis wird einmal angezeigt:

curl -s -X POST $BASE/webhooks -H "Authorization: Bearer $ADMIN_KEY" \
  -H 'Content-Type: application/json' -d '{"url":"https://example.com/einvoice-events"}'

Ereignisse: invoice.issued, delivery.updated, document.received. Wählen Sie sie pro Endpunkt mit "events": ["invoice.issued"] (weggelassen oder leer: alle); URL oder Ereignisse ändern Sie später mit PATCH /webhooks/{id}. Inhalt: {"id": "…", "type": "delivery.updated", "createdAt": "…", "data": {"deliveryId": "…", "status": "DELIVERED", "livemode": "true"}}. livemode ist "false" bei Ereignissen aus der Sandbox.

Antworten Sie binnen 10 Sekunden mit einem beliebigen 2xx. Fehlschläge werden nach 1 min, 5 min, 30 min, 2 h, 6 h und 12 h wiederholt. Ereignisse können doppelt oder in anderer Reihenfolge ankommen: entfernen Sie Doppelte anhand der id.

Jede Anfrage prüfen

Jede Anfrage hat Einvoice-Timestamp (Unix-Sekunden) und Einvoice-Signature: v1=<hex>: den HMAC-SHA256 von <timestamp>.<roher Inhalt> mit Ihrem Geheimnis. Vergleichen Sie in konstanter Zeit und lehnen Sie Zeitstempel ab, die älter als fünf Minuten sind. Nutzen Sie den rohen Inhalt, vor jedem JSON-Parsen.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(raw, ts, sig, secret) {
  if (!secret || !/^\d{1,12}$/.test(ts ?? '') || !/^v1=[0-9a-fA-F]{64}$/.test(sig ?? '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const mac = createHmac('sha256', secret).update(`${ts}.`).update(raw).digest();
  return timingSafeEqual(mac, Buffer.from(sig.slice(3), 'hex'));
}

PHP

function verify(string $raw, ?string $ts, ?string $sig, string $secret): bool {
    if ($secret === '' || preg_match('/^\d{1,12}$/D', $ts ?? '') !== 1
        || preg_match('/^v1=([0-9a-fA-F]{64})$/D', $sig ?? '', $m) !== 1
        || abs(time() - (int) $ts) > 300) return false;
    return hash_equals(hash_hmac('sha256', $ts . '.' . $raw, $secret), strtolower($m[1]));
}

Python

import hashlib, hmac, re, time
def verify(raw: bytes, ts: str | None, sig: str | None, secret: str) -> bool:
    if not secret or not re.fullmatch(r"\d{1,12}", ts or "") or not re.fullmatch(r"v1=[0-9a-fA-F]{64}", sig or ""):
        return False
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig[3:].lower())

Java

static boolean verify(byte[] raw, String ts, String sig, String secret) throws GeneralSecurityException {
    if (secret == null || secret.isEmpty() || ts == null || sig == null
            || !ts.matches("\\d{1,12}") || !sig.matches("v1=[0-9a-fA-F]{64}")
            || Math.abs(Instant.now().getEpochSecond() - Long.parseLong(ts)) > 300) return false;
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    mac.update((ts + ".").getBytes(StandardCharsets.UTF_8));
    return MessageDigest.isEqual(mac.doFinal(raw), HexFormat.of().parseHex(sig.substring(3)));
}

Lehnen Sie die Anfrage ab, wenn kein Geheimnis eingerichtet ist: ein leeres Geheimnis würde gefälschte Signaturen annehmen. Die SDKs bringen das als Hilfsfunktion mit (verifyWebhook, Webhooks::verify, Webhooks.verify, Webhooks.Verify), sobald sie veröffentlicht sind.

Endpunkte verwalten

  • POST /webhooks/{id}/test sendet ein webhook.test-Ereignis an diesen Endpunkt (202): so testen Sie Ihren Empfänger.
  • POST /webhooks/{id}/rotate-secret vergibt sofort ein neues Geheimnis (einmal angezeigt); das alte gilt nicht mehr, hinterlegen Sie das neue also gleich.
  • GET /webhooks/{id}/deliveries zeigt die Zustellungen der letzten 30 Tage; POST /webhooks/{id}/deliveries/{deliveryId}/resend sendet eine abgeschlossene erneut, mit derselben Ereignis-ID (202).

Das Portal bietet all das unter Webhooks.