Kurz gesagt
- Ereignis:
submission.createdfü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
- 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. - 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.
- Ihr Endpunkt erhält einen POST mit den Werten als JSON und drei Headern:
webhook-id,webhook-timestampundwebhook-signature. - 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 }
includePdfhä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-secreterzeugt 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:
| Versuch | Wann | Hinweis |
|---|---|---|
| 1 | Sofort, wenige Sekunden nach der Einsendung | Weiterleitungen werden nicht befolgt. |
| 2 | 1 Minute später | |
| 3 | 5 Minuten später | |
| 4 | 30 Minuten später | |
| 5 | 2 Stunden später | |
| 6 | 6 Stunden später | |
| 7 | 12 Stunden später | Etwa 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 Gone | Keine 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.
| Methode | Pfad | Was er tut |
|---|---|---|
| GET | /forms/{formId}/webhooks | Die Webhooks eines Formulars mit dem Ergebnis der letzten Zustellung. |
| POST | /forms/{formId}/webhooks | Webhook 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}/test | webhook.test jetzt senden und die Antwort zeigen. |
| POST | /forms/{formId}/webhooks/{webhookId}/rotate-secret | Neues Geheimnis. |
| GET | /usage | Wartende 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: