Auswerten
Signierte Zustellungen — und eine E-Mail, die nichts verrät.
Ein Webhook schickt jede neue Antwort an eine https-Adresse Ihrer Wahl.
Jede Zustellung trägt ein Kopffeld X-NumoForms-Signature:
sha256= gefolgt von einem HMAC-SHA256 des exakten
Nachrichtenrumpfs, gebildet mit einem Geheimnis, das nur zu diesem
Webhook gehört. Ihr Endpunkt kann damit belegen, dass die Nutzlast von
uns stammt. Davon getrennt lassen sich ausgewählte E-Mail-Adressen
benachrichtigen, sobald eine Antwort eintrifft — und diese E-Mails
enthalten bewusst keine Antworten.
Was an Ihrem Endpunkt eintrifft
Ein einzelnes JSON-POST je Antwort. Der Rumpf enthält fünf Felder und sonst nichts, sodass Sie einen Parser dafür schreiben können, ohne zu raten.
| Feld | Was es enthält |
|---|---|
event | response.created |
survey_id | Die Umfrage, zu der die Antwort gehört. |
response_id | Die eindeutige Kennung dieser Antwort. |
created_at | Wann die Antwort erfasst wurde. |
answers | Die Antworten, nach Fragekennung geordnet. |
| Kopffeld | Wozu es dient |
|---|---|
X-NumoForms-Event | Der Name des Ereignisses. Heute gibt es eines: response.created. |
X-NumoForms-Signature | sha256= gefolgt vom HMAC-SHA256 des exakten Nachrichtenrumpfs, hexadezimal. |
X-NumoForms-Delivery | Eine eindeutige Kennung für diesen Zustellversuch, damit Sie eine Dopplung erkennen. |
Die Zustellung löst ein Datenbank-Trigger auf der Antworttabelle aus, nicht ein bestimmter Bildschirm der Anwendung. Das wiegt schwerer, als es klingt: Eine Antwort aus einem eingebetteten Formular, über einen gehosteten Link oder aus einer Teilantwort, die Tage später über Speichern und fortsetzen abgeschlossen wurde, nimmt jeweils denselben Weg. Es gibt also keinen Weg, den wir zu verdrahten vergessen hätten.
Der Webhook löst aus, wenn die Antwort eintrifft. Haben Sie Freigaben eingeschaltet, wartet er nicht auf die Entscheidung über Freigabe oder Ablehnung, und bei dieser Entscheidung wird keine zweite Zustellung geschickt. Behandeln Sie eine eingehende Nutzlast als „eine Antwort existiert“ und nicht als „eine Antwort ist gezeichnet“.
Warum die Signatur nicht optional ist
Eine Webhook-Adresse ist ein Geheimnis, das nach außen dringt. Sie landet in einem Ticket, in einer Chatnachricht, in einer Bildschirmaufnahme in einem Übergabedokument, im Browserverlauf eines gemeinsam genutzten Rechners. Ohne Signatur kann jeder, der die Adresse gesehen hat, beliebige Inhalte dorthin schicken, und Ihre Systeme legen sie als echte Antwort einer Beteiligung ab. Für eine Verwaltung, die Einwendungen gegen einen Bauantrag zählt, ist das kein theoretisches Problem.
Deshalb ist jede Zustellung signiert. Jeder Webhook erhält sein eigenes, zufällig erzeugtes Geheimnis, das im Editor hinter der Schaltfläche zum Anzeigen des Signaturgeheimnisses liegt. Wir bilden einen HMAC-SHA256 über die exakten Bytes des Nachrichtenrumpfs mit diesem Geheimnis; Sie bilden ihn mit Ihrer Kopie neu und vergleichen. Eine gefälschte Anfrage scheitert, weil der Fälschung das Geheimnis fehlt.
Die Adresse muss https sein. Das setzt eine Bedingung in
der Datenbank durch und nicht ein Hinweis im Formular, denn ein
Signaturgeheimnis, das über eine unverschlüsselte Verbindung reist, ist
kein Geheimnis.
Eine Zustellung in Node.js prüfen
Zwei Einzelheiten richten in der Praxis den meisten Schaden an. Nehmen Sie den rohen Nachrichtenrumpf und kein ausgewertetes und neu serialisiertes Objekt — Unterschiede beim Leerraum verändern den Hash. Und vergleichen Sie in konstanter Zeit, damit niemand die richtige Signatur aus den Antwortzeiten Byte für Byte erschließen kann.
import { createHmac, timingSafeEqual } from 'node:crypto';
const SECRET = process.env.NOISSIME_WEBHOOK_SECRET;
// rawBody must be the exact bytes received, before any JSON parsing.
// Re-serialising a parsed object changes the whitespace and the check fails.
function isFromNoissime(rawBody, header) {
if (typeof header !== 'string') return false;
const expected =
'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
// timingSafeEqual throws on a length mismatch, so compare lengths first.
return a.length === b.length && timingSafeEqual(a, b);
}
Rufen Sie das auf, bevor Sie irgendetwas auswerten. Ergibt es „falsch“, antworten Sie mit 401 und verwerfen die Anfrage. Ergibt es „wahr“, halten Sie eine Antwort in der Hand, die nur aus Ihrer Umfrage stammen kann.
Das Zustellprotokoll
Ein Webhook, der still aufgehört hat zu arbeiten, sieht genauso aus wie einer, der nie aufgerufen wurde: Es passiert nichts, und nichts sagt es Ihnen. Deshalb wird jeder Versuch in ein Zustellprotokoll geschrieben, und der letzte HTTP-Status steht im Editor neben dem Webhook — grün bei einem 2xx, rot bei allem anderen.
Die Zustellung läuft asynchron, der Status wird also im Nachhinein abgeglichen und nicht im Moment des Absendens geraten. Sie sehen den Status, den Ihr Server tatsächlich zurückgegeben hat. Jeder Webhook lässt sich anhalten und fortsetzen, ohne ihn zu löschen — genau das, was man üblicherweise braucht, während das empfangende System neu aufgespielt wird.
Eine bewusste Grenze: Es gibt keine Wiederholung mit wachsendem Abstand. Eine Zustellung wird einmal versucht, das Ergebnis wird festgehalten, und damit hat es sich. Eine Warteschlange für Wiederholungen zu bauen, die Reihenfolge und Mehrfachzustellung sauber behandelt, ist echte Arbeit, und eine zu behaupten, die wir nicht gebaut haben, wäre schlimmer als dieser Satz. Die Antwort geht dabei nie verloren — sie liegt in der Datenbank, ist in Auswertung und Berichten sichtbar und lässt sich als CSV exportieren.
Ein fehlschlagender Webhook kann auch die Erhebung nicht beschädigen. Wirft die Zustellung einen Fehler, wird er protokolliert und die Antwort trotzdem gespeichert. Worauf es ankommt, ist die Antwort der teilnehmenden Person; die Zustellung ist ein Versuch nach bestem Bemühen.
E-Mail-Benachrichtigungen, ohne die Antworten
Tragen Sie die Adressen ein, die von neuen Antworten erfahren sollen, und sie erhalten beim Eintreffen eine E-Mail. Die Nachricht ist gestaltetes HTML mit einer Nur-Text-Fassung, denn Nur-Text-Programme und Spam-Filter lesen beide den Textteil. Sie nennt die Umfrage, gibt den laufenden Gesamtstand an und verweist auf die Ergebnisse.
Was sie nicht enthält, sind die Antworten. Das ist eine Entscheidung und kein Versäumnis. Eine Benachrichtigungs-E-Mail wird an Kolleginnen weitergeleitet, automatisch in ein gemeinsames Postfach abgelegt, auf ein Telefon abgeglichen und jahrelang in Sicherungen aufbewahrt. Freitextantworten einer Beteiligung in diese Kette zu geben, streut personenbezogene Daten über Systeme, die niemand bewertet hat, und hebt still die Zugriffskontrollen auf, die die Seite zur Sicherheit beschreibt. Die Daten der Teilnehmenden bleiben in der Datenbank, und die E-Mail sagt Ihnen, dass Sie hineinsehen sollen.
Wie Webhooks lösen auch Benachrichtigungen über einen Datenbank-Trigger aus, sie folgen also der Antwort und nicht einem bestimmten Weg der Einreichung. Sind keine Adressen eingetragen, wird nichts geschickt und nichts versucht.
Was das nicht leistet
- Keine Zapier-App und keine Anbindung an Google Sheets.
- Keine Wiederholung mit wachsendem Abstand — ein Versuch je Antwort, danach das Protokoll.
- Keine ausgehenden Ereignisse außer
response.created. Freigaben, Änderungen und Löschungen lösen keine Webhooks aus. - Keine eingehende Schnittstelle, um Umfragen anzulegen oder Antworten programmgesteuert zu lesen.
- Keine Liste erlaubter IP-Adressen für Zustellungen. Die Signatur ist die Authentifizierung.
Häufige Fragen
Wie prüfe ich das Kopffeld X-NumoForms-Signature?
Bilden Sie einen HMAC-SHA256 über den rohen Nachrichtenrumpf mit dem Signaturgeheimnis dieses Webhooks, kodieren Sie ihn hexadezimal, stellen Sie „sha256=“ voran und vergleichen Sie das Ergebnis mit dem Kopffeld in konstanter Zeit. Prüfen Sie, bevor Sie das JSON auswerten, und weisen Sie alles Abweichende mit einem 401 zurück.
Was geschieht, wenn mein Endpunkt beim Eintreffen einer Antwort nicht erreichbar ist?
Die Zustellung wird einmal versucht, und das Ergebnis wird mit dem HTTP-Status im Zustellprotokoll festgehalten. Es gibt keine automatische Wiederholung mit wachsendem Abstand. Die Antwort selbst ist nie in Gefahr: Ein Fehlschlag bei der Zustellung kann das Speichern einer Antwort nicht verhindern, und Sie können die Daten jederzeit über die Ergebnisseite oder einen CSV-Export holen.
Löst ein Webhook auch bei einer Antwort aus einem eingebetteten Formular aus?
Ja. Die Zustellung löst ein Datenbank-Trigger auf der Antworttabelle aus und nicht eine bestimmte Seite der Anwendung. Sie geschieht also unabhängig davon, woher eine Antwort kommt — über einen gehosteten Link, ein eingebettetes Formular oder eine soeben abgeschlossene, zuvor gespeicherte Teilantwort.
Kann ich einen Webhook an eine http://-Adresse schicken?
Nein. Eine Prüfbedingung in der Datenbank verlangt, dass die Adresse mit https:// beginnt, und der Editor lehnt alles andere ab. Ein Signaturgeheimnis, das über eine unverschlüsselte Verbindung läuft, ist kein Geheimnis.
Enthalten Benachrichtigungs-E-Mails die Antworten der Teilnehmenden?
Nein, und das mit Absicht. Die E-Mail teilt mit, dass eine Antwort eingegangen ist, nennt die Umfrage, gibt den laufenden Gesamtstand an und verweist auf die Ergebnisse. Benachrichtigungen werden weitergeleitet und liegen in Postfach-Sicherungen; die Daten der Teilnehmenden bleiben deshalb in der Datenbank, wo die Zugriffskontrollen sind.
Gibt es eine Zapier-App oder eine Anbindung an Google Sheets?
Nein. Webhooks sind heute die einzige ausgehende Anbindung. Eine Zapier-App und eine Anbindung an Google Sheets gibt es nicht, und für beide ist kein Termin veröffentlicht.
Eine Umfrage an Ihre eigenen Systeme anschließen.
Richten Sie einen Webhook auf eine https-Adresse, und jede Antwort kommt dort signiert an — Ihr Empfänger kann also belegen, dass sie von uns stammt und nicht von irgendwem, der die URL gefunden hat. Tragen Sie die Adressen ein, die bei einer eingehenden Antwort eine E-Mail bekommen sollen; die Antworten selbst bleiben in der Datenbank, wo die Zugriffsrechte gelten.
- HMAC-SHA256-Signatur bei jeder Zustellung
- Zustellprotokoll mit dem letzten HTTP-Status
- Ausgelöst von einem Datenbank-Trigger, gleich auf welchem Weg die Antwort kam