Webhook form submissions, explained with working examples
Your form platform fires a POST at a URL. The other end of that POST is the part nobody hands you - so here it is.
A webhook is a POST someone else's server makes for you
Strip the jargon and a webhook is one sentence: when something happens in system A, system A makes an HTTP POST to a URL you chose, with the details in the body. For forms, the something is a submission. Instead of your page posting to a server you run, the form platform itself sends the data wherever you point it - no client-side code, no script tags, nothing the visitor's browser has to load.
That splits every webhook integration into two halves. The sending half is a settings screen in the platform, and every platform documents it. The receiving half is a public URL that has to parse the payload, decide what to keep, store it somewhere useful, and answer quickly - and that half you are usually left to build yourself.
The receiving end is the actual work
A serviceable webhook receiver needs a public HTTPS URL, a parser for whatever shape the sender emits, somewhere durable to put the data, and enough speed that the sender does not time out and retry into duplicates. SheetLink Forms exists so you do not build that. Every form gets a permanent receiver at https://sheetlinkforms.com/f/slf_yourtoken that accepts POST bodies as urlencoded, JSON, or multipart (file parts are currently dropped; bodies are capped at 256 KB), screens them, and appends each accepted one as a row in the Google Sheet or Excel table you connected. The full pipeline is on how it works.
On top of the generic endpoint there are two platform-shaped receivers, /w/webflow/ and /w/framer/, that speak those platforms' native webhook payloads directly. Those are the two worked examples below.
Working example 1: Webflow
Webflow can announce every form submission on a site through one webhook:
- In your Webflow project, open Site settings -> Integrations -> Webhooks.
- Add a webhook with the trigger "Form submission".
- Paste your URL:
https://sheetlinkforms.com/w/webflow/slf_yourtoken. - Submit a test form and watch the row land.
Two details make this pleasant. First, one webhook covers every form on the site - you do not configure each form separately. Second, the form's name is recorded with each submission, so a single sheet can hold your contact form, your newsletter form, and your quote form, with a column telling you which one produced each row. Filter on it, or split tabs later if you prefer.
Working example 2: Framer
Framer wires the webhook per form component instead of per site:
- Select the Form component on your Framer page.
- Set the submission destination to Webhook.
- Paste
https://sheetlinkforms.com/w/framer/slf_yourtokenas the URL. - Publish and send a test submission.
Because the destination lives on the component, different forms on a Framer site can feed different SheetLink forms - and therefore different sheets - by using different tokens. If you want them all in one place instead, reuse the same token everywhere.
Working example 3: anything that can POST
The generic endpoint makes any system with outbound HTTP a webhook sender. From a shell:
curl -X POST https://sheetlinkforms.com/f/slf_yourtoken \
-d "name=Ada Lovelace" -d "email=ada@example.com" -d "source=import-script"From server-side JavaScript:
const res = await fetch("https://sheetlinkforms.com/f/slf_yourtoken", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ada Lovelace", email: "ada@example.com" })
});
const data = await res.json(); // { "ok": true, "id": "..." }Anything that lets you configure an outbound webhook - a CRM, a payment tool, an internal script, a cron job - can point at the same URL and become another source writing rows into the same sheet. Field names that match your column headers map automatically (case and punctuation insensitive); explicit per-field mapping covers the rest.
What happens on our side of the hook
Receiving is more than parsing, and the pipeline behind the URL is the same for webhooks as for browser submissions:
- Screening: per-form and per-IP rate limits, the optional origin allowlist, and content heuristics run on every payload. Suspicious items are quarantined for one-click review in the dashboard - approve delivers the row. Nothing is silently dropped.
- Mapping: fields match column headers automatically, so the payload shape and the sheet shape converge without glue code.
- Delivery: an asynchronous worker writes directly to the Google Sheets API or Microsoft Graph - no Zapier in the middle - and retries at 5 minutes, 30 minutes, and 2 hours if the provider is briefly unreachable.
- A ledger: every attempt appears in the delivery log, and a formula-injection guard escapes leading
=,+,-, and@before values reach cells.
Debugging a webhook that seems silent
The endpoint answers with plain status codes, which most platforms surface in their webhook logs:
200- accepted (JSON senders get{"ok":true,"id":"..."}).400- the body was unreadable. Check the sender's content type.404- unknown token; re-copy the URL from the dashboard.403- the origin allowlist refused the sender. Server-to-server webhooks usually should not be behind an allowlist - loosen it or leave it off for webhook-fed forms.410- the form is paused.413- body over 256 KB.429- rate limited.
If the sender reports success but no row appears, read the delivery log first, then the quarantine queue. To see the end state before wiring anything, the live demo posts into a public sheet, and the docs cover every payload shape. Free during beta - invites via the waitlist.
FAQ
Do webhook submissions go through spam screening too?
Yes - rate limits, content heuristics, and the strictness dial apply to every payload regardless of how it arrived, and suspicious items are quarantined for review rather than dropped. The honeypot only fires if the payload actually contains a filled _slhp field, which server-to-server senders normally never include.
Does one Webflow webhook really cover every form on the site?
Yes. Webflow's "Form submission" trigger fires for all forms in the project, and SheetLink records the form's name with each row. One webhook, one sheet, a column that tells you which form each submission came from.
What JSON shape should my sender emit?
A flat object of field names and values is the shape that maps cleanly: names matching your column headers land automatically, case and punctuation insensitive. For names that differ from your headers, set an explicit per-field mapping in the dashboard.
What response will my sending platform see?
JSON and API-style senders get 200 with {"ok":true,"id":"..."}. Plain HTML form posts get a 303 redirect instead, since a browser is expecting a page. Errors use conventional codes - 404 unknown token, 413 too large, 429 rate limited - which webhook logs display directly.
Is there a size limit on webhook payloads?
Bodies are capped at 256 KB, which is generous for text fields and deliberately not a file channel - multipart is accepted but file parts are currently dropped. Form-submission payloads from Webflow and Framer sit far below the cap in practice.
Related guides: Forms to a spreadsheet without Zapier (and why you might want that) · A contact form on a fully static site · CORS for form endpoints: what actually needs it