Guide

Connect a webhook to your form

Send every clean submission somewhere the moment it lands: an automation tool, a chat channel, or your own server. One signed webhook covers all three.

By Scroll Theory Media

What a webhook does

A webhook is a URL you own that we POST to on each clean submission. The body is JSON, and the request is signed so your receiver can trust it. Webhooks are on the Pro plan. Spam never fires one, and delivery happens after the visitor gets their response, so nothing slows the form down.

Add the URL

Open your form's settings page and paste a URL into the webhooks section. That is the whole setup for the generic case. Every clean submission then arrives at that URL as JSON like this:

{
  "form": "abc123",
  "submission_id": 42,
  "payload": { "email": "jordan@acme.co", "message": "Hello" },
  "meta": { "form_name": "Contact", "country": "US", "ip": "203.0.113.4" }
}

Verify it came from you

Each request carries an X-STF-Timestamp header and an X-STF-Signature header shaped like sha256=<hex>. The signature is an HMAC-SHA256 over timestamp.body, keyed with your form's signing secret from the settings page. Recompute it and compare with a constant-time check:

import crypto from "node:crypto";

const raw = await readRawBody(req);          // the exact bytes we sent
const ts = req.headers["x-stf-timestamp"];
const sig = req.headers["x-stf-signature"];  // "sha256=<hex>"

const expected = "sha256=" + crypto
  .createHmac("sha256", YOUR_FORM_SECRET)
  .update(ts + "." + raw)
  .digest("hex");

const a = Buffer.from(sig || ""), b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
  return respond(401, "bad signature");
}
const { payload } = JSON.parse(raw);

Send it to Zapier, Make, or n8n

Each tool gives you a catch-hook URL. Paste that URL into your form's webhooks section and the tool receives every submission:

  • Zapier: start a Zap with a Catch Hook trigger and copy its URL.
  • Make: add a Custom webhook module and copy its address.
  • n8n: add a Webhook node and copy its production URL.

Post to Slack or Discord

Create an Incoming Webhook URL in Slack, or a webhook URL in a Discord channel's settings, and paste it into the same webhooks section. Submissions post straight into the channel, reshaped automatically to Slack's {text} and Discord's {content} format. Those two are posted in each service's own shape, so they are not signed. Every other URL gets the signed JSON above.

Get your endpoint

Create a form on the free plan and you get an endpoint to drop into any of the examples above. Every submission is stored and spam is quarantined rather than deleted, so a false positive never costs you a real lead.

Start free   Read the docs

More guides