/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.created

Wird 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.sent

Wird 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.paid

Wird 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.overdue

Wird 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.updated

Wird 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.deleted

Wird 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.created

Wird 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.updated

Wird 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.deleted

Wird 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.received

Wird 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.overdue

Wird 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.uploaded

Wird 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.started

Wird 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.failed

Wird 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.requested

Wird 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.decided

Wird 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:

HeaderBeschreibung
X-Invocore-EventEvent-Typ (z. B. invoice.paid)
X-Invocore-SignatureHMAC-SHA256 im Format sha256=<hex>
X-Invocore-DeliveryEindeutige Zustellungs-ID (für Idempotenz und Deduplizierung)
X-Invocore-TimestampISO-8601 Zeitpunkt des ersten Zustellversuchs
User-AgentInvocore-Webhook/1.0
Content-Typeapplication/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).

Python (Flask)
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
Node.js (Express)
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
<?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.