Skip to main content
Pro - configuring and receiving webhook deliveries requires Pro.
Formpaste can 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

  1. Open a form in the dashboardSettingsWebhooks.
  2. Enter your endpoint’s https:// URL (webhook_url) and save.
  3. 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 under data:
  • type / version - the envelope. Only submission.received (version 1) exists today; new event types may be added later.
  • data - your submitted fields, already cleaned: access_key, the botcheck honeypot, and any redirect control 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 is HMAC-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.
Sign over the raw request body bytes, before any JSON re-serialization - re-encoding can reorder keys and break the comparison.

Retries, backoff & delivery log

If your endpoint doesn’t return a 2xx, 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 a webhook_url on Free returns:

Zero-config

No webhook fires until you explicitly set a webhook_url on a Pro-plan form - a new form sends nothing.