CodeB eForms Server von Aloaha

Webhooks Webhooks: jede Einsendung direkt in Ihr System.

Ein Webhook schickt jede neue Einsendung eines Formulars an eine Adresse Ihrer Wahl, wenige Sekunden nachdem jemand auf Absenden geklickt hat: CRM, Ticketsystem, ERP, ein Low-Code-Ablauf oder Ihr eigener Dienst. Die Nachricht ist JSON, enthält alle Werte und auf Wunsch das versiegelte PDF und ist signiert, damit Ihr System prüfen kann, dass sie von Ihrem Formularserver stammt.

Die Signatur folgt der offenen Spezifikation Standard Webhooks, daher können fertige Bibliotheken in vielen Sprachen sie prüfen. Nicht zustellbare Nachrichten werden 21 Stunden lang wiederholt.

Kurz gesagt

  • Ereignis: submission.created für jede echte Einsendung, als JSON, auf Wunsch mit dem versiegelten PDF.
  • Signatur: Standard Webhooks, HMAC-SHA256 über ID, Zeitstempel und Body (Header webhook-id, webhook-timestamp, webhook-signature).
  • Zustellung: 7 Versuche über etwa 21 Stunden, nur HTTPS, private Netze abgelehnt, am Ende eine Fehler-E-Mail.

So funktioniert es

  1. Adresse eintragen: über die REST-API für ein Formular (Rolle admin). Die Antwort enthält ein Signaturgeheimnis, das mit whsec_ beginnt; es wird nur einmal angezeigt.
  2. Jemand sendet das Formular ab: im Browser, aus dem PDF oder über die API. Der Server erstellt wie gewohnt das versiegelte PDF und stellt je Webhook eine Nachricht in die Warteschlange. Die Person wartet nicht auf Ihr System.
  3. Ihr Endpunkt erhält einen POST mit den Werten als JSON und drei Headern: webhook-id, webhook-timestamp und webhook-signature.
  4. Signatur prüfen mit dem Geheimnis, dann innerhalb von 15 Sekunden mit einem beliebigen 2xx-Status antworten. Längere Arbeit erst nach der Antwort erledigen.

Webhooks ergänzen die E-Mails des Formulars: Der Business Owner erhält PDF und XML weiterhin per E-Mail, außer das Formular ist ohne E-Mail eingerichtet.

Einen Webhook einrichten

Sie brauchen ein Access Token eines Benutzers mit der Rolle admin in der Gruppe des Formulars (siehe Bearer-Token erhalten). Die Adresse muss mit https:// beginnen und aus dem Internet erreichbar sein; Adressen in privaten Netzen werden abgelehnt, außer der Administrator erlaubt sie.

curl -X POST https://forms.example.com/api/v1/forms/contact/webhooks \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"url": "https://crm.example.com/hooks/eforms",
       "description": "CRM import",
       "includePdf": true,
       "failureEmails": ["it@example.com"]}'

HTTP/1.1 201 Created
{ "id": "wh_3f2a9c1d0b7e4a6c5d8e", "formId": "contact",
  "url": "https://crm.example.com/hooks/eforms", "events": ["submission.created"],
  "active": true, "includePdf": true, "failureEmails": ["it@example.com"],
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw", … }

# send a test message and see what the receiver answered
curl -X POST -H "Authorization: Bearer $TOKEN" \
  https://forms.example.com/api/v1/forms/contact/webhooks/wh_3f2a9c1d0b7e4a6c5d8e/test
{ "delivered": true, "httpStatus": 200, "durationMs": 184 }
  • includePdf hängt jeder Nachricht das eingesandte PDF als Base64 an (bis 10 MB). Ohne die Option enthält die Nachricht nur die Werte.
  • failureEmails (bis zu fünf Adressen) werden benachrichtigt, wenn eine Nachricht nach allen Versuchen nicht zugestellt werden konnte.
  • Bewahren Sie das Geheimnis im Geheimnisspeicher Ihres Systems auf. Verloren? POST …/webhooks/{id}/rotate-secret erzeugt ein neues; das alte gilt sofort nicht mehr.

Die Nachricht

Jede Nachricht ist ein JSON-Objekt mit type, timestamp und data. Das Ereignis submission.created enthält die Formular-ID, die Einsendungs-ID (dieselbe, die die API bei API-Einsendungen zurückgibt), die Einsendungsreferenz, die Zeit und die Werte nach Feldname. Steuerfelder des Webformulars (Bot-Prüfung, Passwörter des verschlüsselten Archivs) sind nie enthalten.

POST /hooks/eforms HTTP/1.1
Host: crm.example.com
Content-Type: application/json
User-Agent: CodeB-eForms-Server-Webhooks/1.0
webhook-id: msg_2b8f0c6e9a1d4f7b3c5e8a90
webhook-timestamp: 1791802200
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

{ "type": "submission.created",
  "timestamp": "2026-10-12T09:30:00Z",
  "data": {
    "formId": "contact",
    "submissionId": "5d0c2f4e8b1a4c7e9f3b2a1d6e5c4b3a",
    "reference": "contact/118/26",
    "submittedAt": "2026-10-12T09:30:00Z",
    "values": { "FirstName": "Erika", "Surname": "Muster", "Email": "erika@example.com" },
    "pdf": { "fileName": "contact.pdf", "contentType": "application/pdf",
             "size": 48211, "data": "JVBERi0xLjcK…" } } }

Die Testnachricht hat den Typ webhook.test. Die OpenAPI-Beschreibung führt beide Ereignisse unter webhooks mit ihrem JSON Schema, sodass Codegeneratoren die Empfangstypen erzeugen können.

Die Signatur prüfen

Die Signatur ist v1, gefolgt vom Base64-kodierten HMAC-SHA256 über webhook-id + "." + webhook-timestamp + "." + Body. Der Schlüssel ist der Base64-dekodierte Teil des Geheimnisses nach whsec_. Verwenden Sie den unveränderten Body, bevor Sie JSON auswerten, und vergleichen Sie in konstanter Zeit. Lehnen Sie Nachrichten ab, die älter als fünf Minuten sind, und merken Sie sich die webhook-id verarbeiteter Nachrichten, um Wiederholungen zu ignorieren.

Node.js

const crypto = require("node:crypto");

// rawBody: the request body exactly as received (string), before any JSON parsing
function verifyWebhook(secret, headers, rawBody) {
  const id = headers["webhook-id"], ts = headers["webhook-timestamp"];
  if (!id || !ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto.createHmac("sha256", key).update(`${id}.${ts}.${rawBody}`).digest("base64");
  return (headers["webhook-signature"] || "").split(" ").some(part => {
    const [version, sig] = part.split(",");
    return version === "v1" && sig && sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

Python

import base64, hashlib, hmac, time

def verify_webhook(secret: str, headers: dict, raw_body: bytes) -> bool:
    msg_id, ts = headers.get("webhook-id", ""), headers.get("webhook-timestamp", "0")
    if not msg_id or abs(time.time() - int(ts)) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    mac = hmac.new(key, f"{msg_id}.{ts}.".encode() + raw_body, hashlib.sha256).digest()
    expected = base64.b64encode(mac).decode()
    return any(hmac.compare_digest(part[3:], expected)
               for part in headers.get("webhook-signature", "").split() if part.startswith("v1,"))

# or with the reference library:  pip install standardwebhooks
# from standardwebhooks import Webhook;  Webhook(secret).verify(raw_body, headers)

C# (.NET)

using System.Security.Cryptography;
using System.Text;

static bool VerifyWebhook(string secret, string id, string ts, string signatures, string rawBody)
{
    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(ts)) > 300) return false;
    var key = Convert.FromBase64String(secret.StartsWith("whsec_") ? secret.Substring(6) : secret);
    using var hmac = new HMACSHA256(key);
    var expected = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes($"{id}.{ts}.{rawBody}")));
    foreach (var part in signatures.Split(' '))
    {
        var p = part.Split(',', 2);
        if (p.Length == 2 && p[0] == "v1" &&
            CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(p[1]), Encoding.ASCII.GetBytes(expected)))
            return true;
    }
    return false;
}

PHP

function verify_webhook(string $secret, string $id, string $ts, string $signatures, string $rawBody): bool {
    if (abs(time() - (int)$ts) > 300) return false;
    $key = base64_decode(preg_replace('/^whsec_/', '', $secret));
    $expected = base64_encode(hash_hmac('sha256', "$id.$ts.$rawBody", $key, true));
    foreach (explode(' ', $signatures) as $part) {
        [$version, $sig] = array_pad(explode(',', $part, 2), 2, '');
        if ($version === 'v1' && hash_equals($expected, $sig)) return true;
    }
    return false;
}
// $rawBody = file_get_contents('php://input');

Zustellung und Wiederholungen

Eine Nachricht gilt als zugestellt, wenn Ihr Endpunkt innerhalb von 15 Sekunden mit einem 2xx-Status antwortet. Sonst wird sie mit derselben webhook-id erneut gesendet:

VersuchWannHinweis
1Sofort, wenige Sekunden nach der EinsendungWeiterleitungen werden nicht befolgt.
21 Minute später
35 Minuten später
430 Minuten später
52 Stunden später
66 Stunden später
712 Stunden späterEtwa 21 Stunden nach der Einsendung. Danach geht die Fehler-E-Mail hinaus und die Nachricht bleibt 7 Tage für den Administrator gespeichert.
–Antwort 410 GoneKeine weiteren Versuche für diese Nachricht.

Webhooks verwalten

Alle Pfade relativ zu /api/v1, Rolle admin erforderlich. PATCH und DELETE funktionieren auch als POST mit dem Header X-HTTP-Method-Override.

MethodePfadWas er tut
GET/forms/{formId}/webhooksDie Webhooks eines Formulars mit dem Ergebnis der letzten Zustellung.
POST/forms/{formId}/webhooksWebhook anlegen; die Antwort enthält das Geheimnis.
GET/forms/{formId}/webhooks/{webhookId}Ein Webhook.
PATCH/forms/{formId}/webhooks/{webhookId}Adresse, Beschreibung, includePdf, failureEmails ändern oder mit "active": false abschalten (wartende Nachrichten entfallen).
DELETE/forms/{formId}/webhooks/{webhookId}Löschen; wartende Nachrichten entfallen.
POST/forms/{formId}/webhooks/{webhookId}/testwebhook.test jetzt senden und die Antwort zeigen.
POST/forms/{formId}/webhooks/{webhookId}/rotate-secretNeues Geheimnis.
GET/usageWartende Nachrichten und fehlgeschlagene der letzten 7 Tage (Administratoren).

Sicherheit und Datenschutz

  • Nur HTTPS mit Zertifikatsprüfung. Adressen, die auf den Formularserver selbst oder in private Netze zeigen (10.x, 192.168.x, 172.16–31.x, Link-local, Unique Local IPv6), werden abgelehnt, auch wenn ein Hostname später seine Adresse wechselt. Ein Administrator kann interne Empfänger erlauben (Einstellung Webhooks → Allow addresses in private networks).
  • Nur Administratoren der Gruppe eines Formulars können Webhooks anlegen, denn ein Webhook erhält jeden Wert jeder Einsendung.
  • Geheimnisse werden mit dem Windows-Datenschutz des Servers (DPAPI) verschlüsselt gespeichert. Nach einem Umzug auf einen anderen Rechner erneuern Sie sie.
  • Daten auf dem Server: Eine Nachricht bleibt nur bis zur Zustellung auf dem Server. Nachrichten, deren Versuche alle scheitern, werden nach 7 Tagen gelöscht (einstellbar). Darüber hinaus speichert der Server standardmäßig keine Kopie der Einsendungen.
  • Jede Nachricht ist eine gewöhnliche HTTPS-Anfrage Ihres Formularservers; Ihre Firewall kann dessen Adresse freigeben. Bei einem gehosteten Server nennen wir Ihnen die Adresse.

Fragen und Antworten

Was ist ein Formular-Webhook?

Eine HTTPS-Anfrage, die der Formularserver bei jeder Einsendung an Ihr System schickt. Sie enthält die eingesandten Werte als JSON (und auf Wunsch das PDF), sodass Ihr CRM, ERP oder Ablauf die Daten erhält, ohne dass jemand sie aus einer E-Mail abtippt.

Woran erkenne ich, dass ein Webhook wirklich von meinem Formularserver kommt?

Jede Nachricht ist mit HMAC-SHA256 und einem Geheimnis signiert, das nur Sie und der Formularserver kennen, nach der Spezifikation Standard Webhooks. Prüfen Sie den Header webhook-signature wie auf dieser Seite gezeigt oder mit einer Standard-Webhooks-Bibliothek und lehnen Sie Nachrichten ab, die älter als fünf Minuten sind.

Was passiert, wenn mein System nicht erreichbar ist?

Die Nachricht wird nach 1 Minute, 5 Minuten, 30 Minuten, 2, 6 und 12 Stunden mit derselben webhook-id wiederholt. Scheitern alle sieben Versuche, werden die Fehler-E-Mail-Adressen des Webhooks benachrichtigt, und die Nachricht bleibt 7 Tage gespeichert.

Kann ich auch das PDF erhalten?

Ja. Mit "includePdf": true enthält jede Nachricht das eingesandte, versiegelte PDF als Base64, bis 10 MB.

Lösen Einsendungen über die API Webhooks aus?

Ja. Jede echte Einsendung tut das: aus dem Browser, aus dem PDF im Adobe Reader und über die API. Ein PDF mit /fill ausfüllen oder Speichern im Browser löst keinen aus.

Funktionieren Webhooks mit Zapier, Make oder Power Automate?

Ja. Tragen Sie die Catch-Hook- oder HTTP-Trigger-URL des Ablaufs als Webhook-Adresse ein. Kann das Werkzeug keine Signaturen prüfen, nutzen Sie zumindest eine lange, geheime URL und prüfen Sie den webhook-timestamp.

Wie viele Webhooks kann ein Formular haben?

Standardmäßig bis zu 10; der Administrator kann die Grenze ändern.

Formulare anbinden

Fordern Sie Testzugangsdaten an und binden Sie ein Formular unseres Online-Servers an Ihr eigenes System an.

Stand: