All docs
3 min read

Configure a webhook

Webhooks are bound to a form. One form can have many. Each one ships its own URL, signing secret, and event filter.

Create one

  1. Open the form's Webhooks tab.
  2. Click Add webhook.
  3. Pick a provider (Slack, Discord, Sheets, Zapier, n8n, Make) or Generic for your own URL.
  4. Paste the destination URL (HTTPS only).
  5. Choose which events to subscribe to.
  6. Save.

You'll see the signing secret exactly once. Copy it now and stash it in your secrets manager. We never show it again — if you lose it, delete the webhook and create a new one.

Provider vs Generic

Native providers know the destination's shape. We format the payload for them — Slack blocks, Discord embeds, a Sheets row. You don't write a handler.

Generic ships our own JSON envelope to whatever URL you give it. You write the handler, you verify the signature, you transform the payload. This is the right choice for your own backend, a queue worker, a serverless function, or anything custom.

Events

Event Fires when
submission.created A submission lands and passes the spam stack.
submission.flagged A submission is held back as spam or fails captcha.

Most integrations only want submission.created. Subscribe to submission.flagged if you're piping into a moderation queue or want visibility into what's being filtered.

You can change the event list later. Secret stays the same.

Active flag

Every webhook has an active toggle. Flip it off to pause delivery without deleting configuration. Useful when:

  • Your downstream is undergoing maintenance.
  • You're debugging and don't want a flood.
  • You want to keep history but stop new traffic.

Paused webhooks don't queue — events that fire while paused are not delivered later.

Per-form binding

Webhooks live on the form, not the workspace. Cloning a form does not clone its webhooks; you wire them up fresh on the copy. This is intentional — secrets shouldn't silently propagate.

If you want one URL receiving from many forms, create a webhook on each form pointing at the same URL with the same destination service. They'll each have their own secret.

Test from the dashboard

Every webhook has a Send test button. It dispatches a synthetic submission.created with a recognizable test payload (payload.email = "test@formspring.dev"). The test goes through the same signing, retry, and logging path as a real delivery — useful for proving your handler verifies signatures correctly before real submissions land.

Headers your endpoint receives

X-Formspring-Signature: <hex hmac-sha256>
X-Formspring-Event:     submission.created
X-Formspring-Timestamp: 1746651750
Content-Type:           application/json
User-Agent:             Formspring-Webhook/1.0

The signature is computed over the raw request body. See signing for verification.

What's next