Pro - configuring and receiving webhook deliveries requires Pro.
POST every non-spam submission to a URL you control, in real time, signed
so you can verify it actually came from Formpaste.
Enabling a webhook
- Open a form in the dashboard → Settings → Webhooks.
- Enter your endpoint’s
https://URL (webhook_url) and save. - The dashboard shows a signing secret (
whsec_…) for this form - copy it; you’ll need it to verify incoming requests.
When it fires
A webhook fires for each delivered, non-spam submission - the same submissions that generate a notification email. Quarantined (spam) submissions do not fire a webhook. Delivery is best-effort and independent of email: a webhook failure never blocks or affects your email notification.Request
POST to your configured URL, Content-Type: application/json.
Payload
The body is a versioned JSON envelope. Your submitted fields live underdata:
type/version- the envelope. Onlysubmission.received(version1) exists today; new event types may be added later.data- your submitted fields, already cleaned:access_key, thebotcheckhoneypot, and anyredirectcontrol field are stripped and never included.
Fields are nested under
data, not flattened at the top level - in Zapier, Make,
Pipedream, or n8n, map data.email, data.name, etc.Verifying the signature
The signature isHMAC-SHA256 over the string ${timestamp}.${rawBody}, keyed with your
form’s signing secret (whsec_…, shown in the dashboard; regenerate it anytime). Compare it
against X-Formpaste-Signature (sha256=<hex>), and reject requests whose
X-Formpaste-Timestamp is too old for your tolerance.
Retries, backoff & delivery log
If your endpoint doesn’t return a2xx, Formpaste retries with exponential backoff —
roughly 10s → 60s → 5m → 30m, up to 5 attempts total over about 35 minutes, then the
delivery is marked terminal (dead-letter). A 4xx response is treated as a permanent
failure and is not retried - fix your endpoint, then replay it manually. Network errors and
timeouts (10s) are retried the same as a 5xx/429.
Your endpoint should respond 2xx quickly and do slow work asynchronously.
The dashboard shows a per-form delivery log (event, status, HTTP code, timestamp) with a
Replay action to re-send a past delivery once your endpoint is fixed.
Requirements & limits
- The URL must be
https://- plain HTTP is rejected. Loopback, private-network, link-local, and cloud-metadata addresses are rejected as SSRF protection, both on save and again before every send. - One webhook URL per form.
Sending to Slack / Discord
A raw Formpaste webhook cannot post directly to a Slack or Discord webhook URL - those expect their own body shape (Slack:{"text": …}, Discord: {"content": …}) and will
reject the envelope above. Point your Formpaste webhook at an automation platform (Zapier,
Make, Pipedream, n8n) that reshapes the payload into the chat service’s format, then have
that automation post to Slack/Discord. There’s no built-in, paste-and-works Slack/Discord
integration yet - see the Slack and
Discord guides for the webhook-relay recipe.
Google Sheets is a real, native integration - connect a sheet
and rows arrive automatically, no webhook needed. Zapier “native” triggers, Notion, and
Airtable direct integrations are not built yet; for those, the generic webhook above is
the working path today.
Free vs. Pro
Free forms cannot configure a webhook. Attempting to save awebhook_url on Free returns:
Zero-config
No webhook fires until you explicitly set awebhook_url on a Pro-plan form - a new form
sends nothing.