/docs/webhooks
Webhooks-Dokumentation
Vollständige technische Referenz für die 16 Invocore-Events, HMAC-Signatur, Retry-Verhalten und Integrationsbeispiele.
Überblick
Invocore sendet HTTP-POST-Requests an Ihre Endpoint-URL, sobald eines der unterstützten Ereignisse in Ihrer Organisation stattfindet. Jeder Request ist HMAC-SHA256 signiert und wird bei einem Fehler bis zu dreimal mit exponentiellem Backoff (5s → 25s → 125s) wiederholt.
- 16 native Events — vom Rechnungserstellen bis zur Export-Fehlschlagmeldung
- HMAC-SHA256 Signatur pro Request, Timing-Safe-Vergleich vorgesehen
- Retry mit Exponential-Backoff: 5s, 25s, 125s
- Zustellungslog im Admin + One-Click-Retry für fehlgeschlagene Lieferungen
- Bedingungsbasierte Filter (Feld/Operator/Wert) auf Event-Payload
Unterstützte Events
Für jedes Event zeigen wir einen realistischen Sample-Payload. Alle Events haben denselben Umschlag: Felder event, timestamp, organization_id und data.
invoice.createdWird ausgelöst, wenn eine neue Rechnung erstellt wird.
Beispiel-Payload
{
"event": "invoice.created",
"timestamp": "2026-02-25T12:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "inv_01HABC",
"number": "RE-2026-0042",
"status": "draft",
"currency": "EUR",
"issue_date": "2026-02-25",
"due_date": "2026-03-25",
"total_with_vat": "1190.00",
"buyer": {
"name": "Mustermann GmbH",
"email": "invoice@mustermann.de"
}
}
}invoice.sentWird ausgelöst, wenn eine Rechnung per E-Mail oder Peppol versendet wird.
Beispiel-Payload
{
"event": "invoice.sent",
"timestamp": "2026-02-25T12:05:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "inv_01HABC",
"number": "RE-2026-0042",
"status": "sent",
"channel": "email",
"sent_to": "invoice@mustermann.de"
}
}invoice.paidWird ausgelöst, wenn eine Rechnung als bezahlt markiert wurde.
Beispiel-Payload
{
"event": "invoice.paid",
"timestamp": "2026-03-01T09:30:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "inv_01HABC",
"number": "RE-2026-0042",
"status": "paid",
"paid_at": "2026-03-01T09:30:00Z",
"paid_amount": "1190.00",
"payment_method": "bank_transfer"
}
}invoice.overdueWird ausgelöst, wenn das Fälligkeitsdatum einer Rechnung überschritten wurde.
Beispiel-Payload
{
"event": "invoice.overdue",
"timestamp": "2026-03-26T00:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "inv_01HABC",
"number": "RE-2026-0042",
"status": "overdue",
"overdue_days": 1,
"due_date": "2026-03-25"
}
}invoice.updatedWird ausgelöst, wenn eine Rechnung bearbeitet wurde.
Beispiel-Payload
{
"event": "invoice.updated",
"timestamp": "2026-02-25T14:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "inv_01HABC",
"number": "RE-2026-0042",
"total_with_vat": "1309.00"
}
}invoice.deletedWird ausgelöst, wenn eine Rechnung gelöscht wurde.
Beispiel-Payload
{
"event": "invoice.deleted",
"timestamp": "2026-02-25T15:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "inv_01HABC",
"number": "RE-2026-0042"
}
}contact.createdWird ausgelöst, wenn ein neuer Kontakt angelegt wird.
Beispiel-Payload
{
"event": "contact.created",
"timestamp": "2026-02-25T10:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "con_01HDEF",
"firma_name": "Mustermann GmbH",
"email": "info@mustermann.de",
"vat_id": "DE123456789"
}
}contact.updatedWird ausgelöst, wenn ein Kontakt aktualisiert wird.
Beispiel-Payload
{
"event": "contact.updated",
"timestamp": "2026-02-25T11:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "con_01HDEF",
"firma_name": "Mustermann GmbH",
"email": "billing@mustermann.de"
}
}contact.deletedWird ausgelöst, wenn ein Kontakt gelöscht wird.
Beispiel-Payload
{
"event": "contact.deleted",
"timestamp": "2026-02-25T16:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "con_01HDEF",
"firma_name": "Mustermann GmbH"
}
}payment.receivedWird ausgelöst, wenn eine Zahlung verbucht wird (Stripe, PayPal, manuell).
Beispiel-Payload
{
"event": "payment.received",
"timestamp": "2026-03-01T09:30:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "pay_01HGHI",
"invoice_id": "inv_01HABC",
"amount": "1190.00",
"currency": "EUR",
"provider": "stripe",
"paid_at": "2026-03-01T09:30:00Z"
}
}payment.overdueWird ausgelöst, wenn ein Zahlungsziel überschritten und eine Mahnung fällig ist.
Beispiel-Payload
{
"event": "payment.overdue",
"timestamp": "2026-03-26T00:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"invoice_id": "inv_01HABC",
"invoice_number": "RE-2026-0042",
"overdue_amount": "1190.00",
"currency": "EUR",
"overdue_days": 1
}
}document.uploadedWird ausgelöst, wenn ein Dokument hochgeladen wird.
Beispiel-Payload
{
"event": "document.uploaded",
"timestamp": "2026-02-25T13:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "doc_01HJKL",
"filename": "rechnung_januar.pdf",
"file_size_bytes": 245760,
"mime_type": "application/pdf",
"uploaded_by": "user@example.com"
}
}export.startedWird ausgelöst, wenn ein Export-Job startet (DATEV, CSV, Excel).
Beispiel-Payload
{
"event": "export.started",
"timestamp": "2026-02-25T20:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "exp_01HMNO",
"format": "datev",
"started_at": "2026-02-25T20:00:00Z"
}
}export.failedWird ausgelöst, wenn ein Export-Job fehlschlägt.
Beispiel-Payload
{
"event": "export.failed",
"timestamp": "2026-02-25T20:01:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "exp_01HMNO",
"format": "datev",
"error": "No documents found in selected period."
}
}approval.requestedWird ausgelöst, wenn eine Rechnung zur Freigabe eingereicht wird.
Beispiel-Payload
{
"event": "approval.requested",
"timestamp": "2026-02-25T12:30:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "apr_01HPQR",
"invoice_id": "inv_01HABC",
"submitted_by": "user@example.com",
"submitted_at": "2026-02-25T12:30:00Z"
}
}approval.decidedWird ausgelöst, wenn über eine Freigabe entschieden wird (approved / rejected).
Beispiel-Payload
{
"event": "approval.decided",
"timestamp": "2026-02-25T13:00:00Z",
"organization_id": "org_01HXYZ",
"data": {
"id": "apr_01HPQR",
"invoice_id": "inv_01HABC",
"decision": "approved",
"decided_by": "manager@example.com",
"decided_at": "2026-02-25T13:00:00Z"
}
}HTTP-Header
Jeder Request enthält folgende Header:
| Header | Beschreibung |
|---|---|
X-Invocore-Event | Event-Typ (z. B. invoice.paid) |
X-Invocore-Signature | HMAC-SHA256 im Format sha256=<hex> |
X-Invocore-Delivery | Eindeutige Zustellungs-ID (für Idempotenz und Deduplizierung) |
X-Invocore-Timestamp | ISO-8601 Zeitpunkt des ersten Zustellversuchs |
User-Agent | Invocore-Webhook/1.0 |
Content-Type | application/json; charset=utf-8 |
Signatur-Verifikation
Verifizieren Sie jede eingehende Anfrage, bevor Sie den Payload verarbeiten. Verwenden Sie einen timing-sicheren Vergleich (z. B. hmac.compare_digest, crypto.timingSafeEqual oder hash_equals).
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"
@app.route("/webhooks/invocore", methods=["POST"])
def invocore_webhook():
signature_header = request.headers.get("X-Invocore-Signature", "")
if not signature_header.startswith("sha256="):
abort(401)
expected = hmac.new(
WEBHOOK_SECRET.encode(),
request.get_data(),
hashlib.sha256,
).hexdigest()
received = signature_header.split("=", 1)[1]
if not hmac.compare_digest(expected, received):
abort(401)
event = request.headers.get("X-Invocore-Event")
payload = request.json
# ... handle payload["data"] ...
return "", 200
import express from "express";
import crypto from "crypto";
const app = express();
const WEBHOOK_SECRET = process.env.INVOCORE_WEBHOOK_SECRET;
// Use raw body so the signature can be computed byte-exact
app.post(
"/webhooks/invocore",
express.raw({ type: "application/json" }),
(req, res) => {
const signatureHeader = req.header("X-Invocore-Signature") || "";
if (!signatureHeader.startsWith("sha256=")) return res.status(401).end();
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
const received = signatureHeader.slice("sha256=".length);
const sigMatch =
expected.length === received.length &&
crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex")
);
if (!sigMatch) return res.status(401).end();
const event = req.header("X-Invocore-Event");
const payload = JSON.parse(req.body.toString("utf8"));
// ... handle payload.data ...
res.status(200).end();
}
);
<?php
$webhookSecret = getenv('INVOCORE_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$signatureHeader = $_SERVER['HTTP_X_INVOCORE_SIGNATURE'] ?? '';
if (strpos($signatureHeader, 'sha256=') !== 0) {
http_response_code(401);
exit;
}
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
$received = substr($signatureHeader, strlen('sha256='));
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}
$event = $_SERVER['HTTP_X_INVOCORE_EVENT'] ?? '';
$payload = json_decode($rawBody, true);
// ... handle $payload['data'] ...
http_response_code(200);
Retry-Verhalten & Idempotenz
Bei jeder Antwort außerhalb von 2xx (oder bei Netzwerkfehler) wird der Zustellversuch bis zu dreimal wiederholt: nach 5 Sekunden, dann 25 Sekunden, dann 125 Sekunden. Die Header X-Invocore-Delivery bleibt über alle Versuche identisch — Sie können damit Duplikate ausfiltern und Ihre Handler idempotent gestalten. Nach drei fehlgeschlagenen Versuchen bleibt die Zustellung im Status failed; Sie können sie manuell aus dem Admin-Panel erneut versenden.
Bereit zum Einrichten?
Webhooks richten Sie in unter fünf Minuten im Admin-Panel ein — mit visuellem Builder, Templates für Slack / Zapier / Teams / Make und einem integrierten Test-Endpunkt.