Webhooks.
Every accepted submission can be pushed to an endpoint of yours as a JSON POST — signed, retried, and outside the request the visitor waits for. Configure one in the dashboard under „Integrations"; several webhooks per form are fine.
The payload
The body is the submission as stored: the form's name, the timestamp, and your own fields. Reserved fields are gone by then — they steered the pipeline and were never stored.
Verifying the signature
With a secret configured, every delivery carries an X-Signature header: the HMAC-SHA256 of the raw body, hex-encoded. Compute it over the bytes you received — not over a re-serialised object, which would differ in whitespace and key order.
Compare in constant time, as above. A plain string comparison leaks, byte by byte, how much of a guessed signature was right.
Delivery and retries
Delivery happens in the background queue, one job per webhook, so a slow or broken endpoint never delays the visitor's response. Any status outside 2xx counts as a failure and is retried up to five times with exponential backoff — one second, two, four, and so on, capped at five minutes. After that the job is parked as failed.
Because of the retries your endpoint has to tolerate the same submission arriving twice. Answer 2xx as soon as you have accepted the payload and do the actual work afterwards — a slow handler turns into a retry, and the retry into a duplicate.
Other targets
The same event also feeds the ready-made integrations, so you do not have to build a receiver for the common cases. Each one is configured in the dashboard and its credentials are encrypted at rest — they are never returned by the API.
- Slack — the submission as a channel message.
- Discord — same, into a Discord channel.
- Google Sheets — one row per submission, new fields become new columns.
- Notion — one page per submission in a database.
- Airtable — one record per submission.