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}/testsendet einwebhook.test-Ereignis an diesen Endpunkt (202): so testen Sie Ihren Empfänger.POST /webhooks/{id}/rotate-secretvergibt sofort ein neues Geheimnis (einmal angezeigt); das alte gilt nicht mehr, hinterlegen Sie das neue also gleich.GET /webhooks/{id}/deliverieszeigt die Zustellungen der letzten 30 Tage;POST /webhooks/{id}/deliveries/{deliveryId}/resendsendet eine abgeschlossene erneut, mit derselben Ereignis-ID (202).
Das Portal bietet all das unter Webhooks.