v1 · Public PreviewOutboundHMAC-SHA256

Pfeffersack Webhooks

Pfeffersack ruft eine von dir definierte URL auf, sobald in deinem Konto etwas Relevantes passiert — z.B. eine Rechnung erstellt oder bezahlt wird. So lassen sich Pfeffersack-Events ohne eine Zeile Code in n8n, Zapier oder Make weiterverarbeiten.

Webhook einrichten

  1. In deinem Workflow-Tool (n8n / Zapier / Make) einen Webhook-Trigger anlegen und dessen URL kopieren.
  2. In Pfeffersack unter Einstellungen → Integrationen → n8n auf Webhook hinzufügen klicken, URL einfügen und Events auswählen.
  3. Das einmalig angezeigte Secret in deinem Workflow speichern. Es wird zur Verifikation der Signatur benötigt.
  4. Über die Schaltfläche Test senden einen webhook.test-Event auslösen, um die Verbindung end-to-end zu prüfen.

Request-Format

Jeder Webhook ist ein POST mit Content-Type: application/json. Body-Struktur:

{
  "id": "9c1c2b3a-…",        // event_id (UUID, stabil über Retries)
  "type": "invoice.paid",     // siehe Event-Tabelle unten
  "created_at": "2026-05-12T14:23:11.000Z",
  "data": {                   // pro Event-Typ unterschiedlich
    "invoice_id": 42,
    "source": "invoices",
    "invoice_number": "2026-0017",
    "amount": 1250,
    "currency": "CHF",
    "paid_at": "2026-05-12T14:23:11.000Z"
  }
}

Headers

Pfeffersack sendet folgende Header bei jeder Lieferung:

HeaderInhalt
X-Pfeffersack-Signaturet=<unix_seconds>,v1=<hex_hmac>[,v1=<hex_hmac>]
X-Pfeffersack-EventEvent-Typ (z.B. invoice.paid)
X-Pfeffersack-Event-IdUUID des logischen Events (gleich über alle Retries)
X-Pfeffersack-DeliveryUUID dieses Versuchs (eindeutig pro HTTP-Call)
X-Pfeffersack-Webhook-IdUUID deines Webhooks
User-AgentPfeffersack-Webhooks/1.0

Signatur-Verifikation

Die Signatur ist ein HMAC-SHA256 über den exakten String ${timestamp}.${rawBody}, signiert mit deinem Webhook-Secret. Receiver sollten in dieser Reihenfolge prüfen:

  1. Body als raw text (nicht JSON-geparst) extrahieren.
  2. Header parsen: t = Timestamp, v1 = Signaturen.
  3. Wenn |now - t| > 300s (Sekunden) → ablehnen (Replay-Schutz).
  4. Erwartete Signatur lokal berechnen: HMAC_SHA256(secret, t + "." + rawBody).
  5. Wenn keine der gelieferten v1=-Signaturen timing-safe matched → ablehnen.

Beispiel-Verifikation in Node.js:

import { createHmac, timingSafeEqual } from "crypto";

function verifyPfeffersack(rawBody, sigHeader, secret) {
  const parts = Object.fromEntries(
    sigHeader.split(",").map((p) => p.split("="))
  );
  const t = parseInt(parts.t, 10);
  if (!Number.isFinite(t)) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`, "utf8")
    .digest("hex");

  // Header kann mehrere v1=… enthalten (während einer Secret-Rotation):
  const sigs = sigHeader.split(",")
    .filter((p) => p.startsWith("v1="))
    .map((p) => p.slice(3));

  return sigs.some((s) =>
    s.length === expected.length &&
    timingSafeEqual(Buffer.from(s, "hex"), Buffer.from(expected, "hex"))
  );
}

Beispiel-Verifikation in PHP (für Make / Zapier Function-Steps):

function verifyPfeffersack(string $rawBody, string $sigHeader, string $secret): bool {
    preg_match('/t=(\d+)/', $sigHeader, $tm);
    if (empty($tm[1])) return false;
    $t = (int) $tm[1];
    if (abs(time() - $t) > 300) return false;

    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);

    preg_match_all('/v1=([0-9a-f]{64})/', $sigHeader, $matches);
    foreach ($matches[1] as $sig) {
        if (hash_equals($expected, $sig)) return true;
    }
    return false;
}

Event-Typen

TypeWann?Daten
webhook.testManuell via Test senden-Knopf.{ note?: string }
invoice.createdSobald eine neue Rechnung in Pfeffersack angelegt wird (GmbH oder Einzelunternehmen).{ invoice_id, source, invoice_number, invoice_date, amount, currency, customer_id }
invoice.paidSobald eine Rechnung als bezahlt markiert wird.{ invoice_id, source, invoice_number, amount, currency, paid_at }

Payloads enthalten bewusst nur IDs + Summary. Vollständige Daten holst du anschliessend über die Pfeffersack REST API mit einem API-Key.

Auslieferungs-Verhalten

  • Ein Versuch gilt als erfolgreich, wenn dein Endpoint mit HTTP 2xx antwortet — idealerweise innerhalb von 10 Sekunden.
  • Sofort-Auslieferung: Sobald in Pfeffersack ein Event entsteht (z.B. eine Rechnung wird erstellt), wird der Webhook direkt gesendet — typischerweise innerhalb von einer Sekunde.
  • Manuelle Synchronisation: Schlägt die Sofort-Auslieferung fehl (Empfänger gerade down, Netzwerk-Blip, 4xx/5xx), bleibt das Event in der Warteschlange. In der Integrations-UI siehst du dann eine Anzeige „X Lieferungen ausstehend" mit einem Synchronisieren-Knopf, der alle wartenden Events erneut auszuliefern versucht.
  • Nach 5 fehlgeschlagenen Versuchen wird das Event als endgültig fehlgeschlagen markiert. Es wird nicht mehr automatisch retried — du kannst es nach Behebung des Receivers mit einem erneuten Klick auf Synchronisieren entfernen lassen (in einer späteren Version; aktuell bleibt es als „failed" im Log sichtbar).
  • Nach 20 aufeinanderfolgenden Fehlern wird die Subscription automatisch deaktiviert. Du reaktivierst sie mit dem Toggle in der UI — der Fehlerzähler wird dabei zurückgesetzt.
  • X-Pfeffersack-Event-Id bleibt über alle Versuche identisch. Receiver müssen dedupen.

Secret-Rotation

Über Secret rotieren generierst du jederzeit ein neues Secret. Während einer 24-Stunden-Grace-Period sendet Pfeffersack jede Lieferung mit beiden Signaturen (neu + alt). So kannst du den Schlüssel in n8n in Ruhe austauschen, ohne dass Lieferungen abreissen.

Sicherheits-Hinweise

  • Webhook-URLs müssen https:// verwenden. Interne Adressen (RFC 1918, Loopback, Link-Local, Cloud-Metadata) werden vor jedem Versuch geprüft und blockiert (SSRF-Schutz).
  • Speichere das Secret nur in n8n / einem Secret-Manager. Es taucht nirgendwo im Pfeffersack-UI nach dem ersten Anzeigen wieder auf.
  • Pfeffersack speichert eingegangene HTTP-Antwortbodies bis zu 4 KB im Lieferverlauf — vermeide es daher, vertrauliche Daten in den Response-Body deines Endpoints zu schreiben.
  • Dein Endpoint sollte mit 200 OK antworten,bevor du langlaufende Folgearbeit startest. Sonst läuft die 10-Sekunden-Timeout-Grenze ab und der Event wird unnötig retried.

Bereit? Erstelle deinen ersten Webhook unter Einstellungen → Integrationen → n8n.