# Individuelle Webhooks

Einreichungsdaten per POST an einen beliebigen HTTPS-Endpunkt senden — dein Backend, eine serverlose Funktion oder einen Proxy.

## Individuelle Webhooks

Sende jede abgeschlossene Einreichung als signierten POST-Request an einen beliebigen HTTPS-Endpunkt — dein Backend, eine Automatisierungsplattform oder eine serverlose Funktion.

<h2 id="how-it-works">So funktioniert es</h2>

<p>
  Bei jeder Einreichung sendet Formstep einen JSON-Body per POST an deine Webhook-URL. Der Request wird mit HMAC-SHA256 signiert, bei
  Fehlern wiederholt und im Ereignisprotokoll der Integration erfasst. Diese Seite behandelt die Einrichtung. Die genaue Nutzlast, die
  Header und der Signaturalgorithmus stehen in der <a href="/de/developers/webhooks-reference">Webhook-Referenz</a>.
</p>

<h2 id="add">Webhook hinzufügen</h2>

> ℹ️ **Mehrere Webhooks**
> <p>Du kannst einem Formular mehrere Webhooks zuweisen. Jeder wird bei jedem Ereignis unabhängig ausgelöst.</p>

<h2 id="url-rules">Welche URLs Formstep akzeptiert</h2>

<ul>
  <li>
    Überall <code>https://</code>, oder <code>http://</code> für <code>localhost</code> und <code>*.localhost</code> während der
    Entwicklung.
  </li>
  <li>Keine Zugangsdaten in der URL, und höchstens 2.048 Zeichen.</li>
  <li>
    Keine privaten oder internen Adressen. Der Hostname wird unmittelbar vor <em>jeder</em> Zustellung aufgelöst und erneut geprüft, sodass
    ein DNS-Eintrag, der nach der Einrichtung auf eine interne Adresse umgebogen wird, weiterhin abgelehnt wird.
  </li>
</ul>

<p>
  Eine abgelehnte URL ist ein Konfigurationsproblem, kein vorübergehendes: Die Zustellung schlägt endgültig fehl, statt wiederholt zu
  werden.
</p>

<h2 id="payload">Was du erhältst</h2>

<p>
  Jeder Request ist derselbe Ereignis-Umschlag — <code>id</code>, <code>type</code>, <code>createdAt</code>, <code>apiVersion</code>,{' '}
  <code>test</code> und <code>data</code>. In <code>data</code> stehen das Formular, die Einreichung (ID, E-Mail der befragten Person,
  Zeitpunkt der Einreichung, PDF-Link, Sprache), ein <code>answers</code>-Objekt, das nach{' '}
  <a href="/de/requests/field-keys">Feldschlüssel</a> benannt ist, und ein <code>display</code>-Objekt mit denselben Schlüsseln als lesbarer
  Text. Jede Antwort erscheint einmal, in jeder Map.
</p>

<p>
  Die vollständige <a href="/de/developers/webhooks-reference#payload">Nutzlastform</a>,{' '}
  <a href="/de/developers/webhooks-reference#fields-vs-answers">answers und display</a>, und wie eine{' '}
  <a href="/de/building-forms/repeating-groups">Wiederholungsgruppe</a> dargestellt wird, findest du in der Referenz.
</p>

<p>
  Um den genauen Body für dein Formular als Vorschau zu sehen, öffne die Integration und klappe <strong>Beispiel-Nutzlast</strong> unter dem
  Signing-Secret auf. Sie rendert deine aktuelle Zuordnung mit Beispielantworten.
</p>

<h2 id="signatures">Signaturen prüfen</h2>

<p>
  Jeder Request enthält einen <code>X-Formstep-Signature</code>-Header: <code>t=TIMESTAMP,sha256=HEX</code>, ein HMAC-SHA256 von{' '}
  <code>TIMESTAMP.BODY</code>, berechnet mit deinem Signing-Secret. Das Secret selbst wird nie gesendet. Die Referenz enthält ein{' '}
  <a href="/de/developers/webhooks-reference#signing">kopierbares Prüf-Snippet</a>.
</p>

> ⚠️ **In der Produktion immer verifizieren**
> <p>
>     Ohne Verifizierung kann jeder, der deine URL kennt, gefälschte Einreichungen senden. Lehne Requests mit fehlender oder ungültiger
>     Signatur ab.
>   </p>

<h2 id="abandoned-responses">Ereignisse bei abgebrochenen Antworten</h2>

<p>
  Individuelle Webhooks werden nur bei abgeschlossenen Einreichungen und Bearbeitungen ausgelöst — nie bei abgebrochenen Entwürfen. Ein
  individueller Webhook ist der einzige Empfänger, der beide bekommt: Ein Zapier-, Make- oder n8n-Abonnement wählt entweder erste
  Einreichungen oder Bearbeitungen, nie beide. Nutze für abgebrochene Entwürfe einen Anbieter mit einem Schritt{' '}
  <strong>Abgebrochene Einreichungen</strong>: Google Sheets, Airtable, Notion, Slack, Discord, Linear oder GitHub Issues. Jeder hat ein
  eigenes Leerlauffenster und, sofern zutreffend, eine eigene Vorlage. Dieser Schritt erfordert Pro oder Business.
</p>

<h2 id="retries">Wiederholungsversuche und Fehler</h2>

<ul>
  <li>
    Eine Zustellung gilt bei jedem <code>2xx</code> als erfolgreich.
  </li>
  <li>
    Bis zu 5 Versuche: Der erste erfolgt sofort, Wiederholungen warten mindestens 1, 2, 4 und 8 Minuten. Formstep sucht alle 30 Minuten nach
    fälligen Wiederholungen, sodass der letzte Versuch etwa zwei Stunden nach dem ersten erfolgt. Ein <code>Retry-After</code>-Header bei
    einem <code>429</code> oder <code>5xx</code> wird befolgt, wenn er eine längere Wartezeit verlangt.
  </li>
  <li>
    <code>429</code>, <code>5xx</code>, Timeouts und Verbindungsfehler werden wiederholt. Jeder andere <code>4xx</code>-Fehler schlägt
    sofort fehl.
  </li>
  <li>
    Nach 5 aufeinanderfolgenden Fehlern pausiert die Integration automatisch, und die Person, die sie eingerichtet hat, erhält eine E-Mail.
    Behebe den Endpunkt und drücke dann <strong>Fortsetzen</strong>.
  </li>
  <li>
    <code>401</code>, <code>403</code> und <code>404</code> stoppen die Integration sofort mit einem Fehlerstatus und derselben E-Mail —
    ohne auf fünf Fehler zu warten.
  </li>
  <li>
    Zustellungen, die alle 5 Versuche aufgebraucht haben, sammeln sich in einem Banner auf der Integration.{' '}
    <strong>Alle erneut versuchen</strong> reiht sie erneut ein und reaktiviert eine pausierte Integration.
  </li>
</ul>

<h2 id="testing">Testen</h2>

<p>
  <strong>Testereignis senden</strong> erscheint im Schritt Fertigstellen und erneut auf der gespeicherten Integration. Es sendet eine
  synthetisierte Beispieleinreichung per POST — <code>"John Doe"</code> für Text, <code>42</code> für Zahlen, <code>john@example.com</code>{' '}
  für E-Mail — signiert und mit deinen benutzerdefinierten Headern, genau wie eine echte Zustellung. Von der gespeicherten Integration aus
  schreibt es außerdem einen Verbindungstest-Eintrag in das Ereignisprotokoll.
</p>

<p>Für lokale Entwicklung kannst du deinen Dev-Server mit einem Tunnel zugänglich machen:</p>

```
# ngrok
ngrok http 3000

# cloudflare tunnel

cloudflared tunnel --url http://localhost:3000
```

<h2 id="faq">FAQ</h2>

  <p>
    Ja. Jeder hat seine eigene URL, sein eigenes Signing-Secret und eigene benutzerdefinierte Header. Alle aktiven Webhooks werden bei jeder
    Einreichung unabhängig ausgelöst.
  </p>

  <p>
    Ja — bis zu 5, hinzugefügt während der Einrichtung oder später über die Integration. <code>Content-Type</code> wird automatisch gesetzt,
    und jeder Versuch, ihn zu überschreiben, wird ignoriert.
  </p>

  <p>
    Nein. Das Signing-Secret wird einmalig bei der Erstellung des Webhooks generiert und kann nicht geändert werden. Wenn du ein neues
    Secret brauchst, lösche den Webhook und erstelle einen neuen.
  </p>

  <p>Ja. Öffne die Integration, bearbeite die URL und speichere. Das Signing-Secret und der Ereignisverlauf bleiben erhalten.</p>

  <p>
    Nur für <code>localhost</code> und <code>*.localhost</code> während der Entwicklung. Alle anderen URLs müssen HTTPS verwenden.
  </p>

  <p>
    Lösche sie unter Formulareinstellungen → Integrationen. Formstep stellt das Senden von Requests sofort ein, und der Ereignisverlauf der
    Integration wird mit ihr gelöscht.
  </p>

<h2 id="next-steps">Nächste Schritte</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Webhook-Referenz](/de/developers/webhooks-reference) — Nutzlast, Header und Signaturprüfung
  - [Airtable](/de/integrations/airtable) — Einreichungen in eine Airtable-Base übertragen
  - [Linear](/de/integrations/linear) — Einreichungen in Linear-Issues umwandeln
  - [Wiederholungsgruppen](/de/building-forms/repeating-groups) — Befragte so viele Einträge hinzufügen lassen, wie sie brauchen
</div>
