Google Forms Webhook Generator
Paste any HTTPS endpoint. Get a copy-paste Google Apps Script that POSTs every form response to it with an onFormSubmit trigger. Pick from three payload shapes, add a bearer token if you need auth, ship in two minutes.
Build your Apps Script
Paste any HTTPS endpoint. Pick a payload shape. The script regenerates as you type.
Any HTTPS endpoint that accepts application/json. For a Slack channel, use the Slack-formatted generator instead.
Flat keys plus a _meta block (formId, responseId, submittedAt). Safe to retry / dedupe.
Sent as Authorization: Bearer <token> on every POST.
// Google Forms → Webhook bridge
//
// Generated by https://routeforms.com/tools/google-forms-webhook-generator
//
// Install (90 seconds):
// 1. In your Google Form, click the three-dot menu → Apps Script.
// 2. Replace the existing function myFunction() {} with the contents of
// this file, then save (Cmd+S).
// 3. In the function dropdown at the top of the editor, select
// installFormWebhook and click Run.
// 4. When Google asks, click Advanced → Go to ... (unsafe) → Allow.
// (You're authorising your own script — read it first if you'd like.)
// 5. Submit a test response in your Google Form to verify end-to-end.
const WEBHOOK_URL = "PASTE_YOUR_WEBHOOK_URL_HERE";
const TEST_MODE = false;
function installFormWebhook() {
var form = FormApp.getActiveForm();
if (!form) {
throw new Error(
"No active form. Open this script editor from inside your Google Form: " +
"Form → three-dot menu → Apps Script."
);
}
var existing = ScriptApp.getProjectTriggers();
for (var i = 0; i < existing.length; i++) {
if (existing[i].getHandlerFunction() === "onFormSubmit") {
ScriptApp.deleteTrigger(existing[i]);
}
}
ScriptApp.newTrigger("onFormSubmit").forForm(form).onFormSubmit().create();
Logger.log("\u2713 Webhook trigger installed. Submit a test response to verify.");
}
function collectWithMeta(e) {
var form = FormApp.getActiveForm();
var response = e.response;
var itemResponses = response.getItemResponses();
var data = {};
for (var i = 0; i < itemResponses.length; i++) {
var ir = itemResponses[i];
var title = ir.getItem().getTitle();
var answer = ir.getResponse();
if (Array.isArray(answer)) answer = answer.join(", ");
data[title] = answer;
}
data._meta = {
formTitle: form.getTitle(),
formId: form.getId(),
responseId: response.getId ? response.getId() : null,
submittedAt: new Date().toISOString()
};
return data;
}
function onFormSubmit(e) {
try {
var payload = collectWithMeta(e);
if (TEST_MODE) {
Logger.log("TEST MODE \u2014 would have POSTed:");
Logger.log(JSON.stringify(payload, null, 2));
return;
}
var options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
var res = UrlFetchApp.fetch(WEBHOOK_URL, options);
var code = res.getResponseCode();
if (code < 200 || code >= 300) {
Logger.log("Webhook returned " + code + ": " + res.getContentText());
}
} catch (err) {
Logger.log("Webhook bridge error: " + err);
}
}
Paste a webhook URL above to enable Copy and Download.
A Google Forms webhook in three minutes flat
Google Forms has no native webhook setting. The path is always: attach an Apps Script to the form, listen for onFormSubmit, POST to the URL. This generator skips the writing-it-yourself part.
- onFormSubmit trigger, installed for you by
installFormWebhook()so you can't accidentally pick “On open” or “From spreadsheet”. - Three payload shapes — flat (one key per question), Apps Script event shape (matches namedValues), or flat +
_metablock with formId, responseId, and submittedAt. - Optional bearer auth, sent as
Authorization: Bearer …on every POST. Tokens stay in your browser; they're only baked into the script you download. - TEST_MODE, swaps the live POST for an Apps Script
Logger.logcall so you can verify the payload shape in the Executions log before going live. - Non-2xx response logging, the script captures the HTTP code and body when the endpoint rejects, so silent failures aren't silent.
From paste to live webhook in 90 seconds
The install procedure is identical regardless of payload shape.
- 1Open the Apps Script editor inside your Google FormIn your Google Form, click the three-dot menu (top-right) → Apps Script. A new tab opens with a blank script template.
- 2Replace the placeholder with the generated scriptDelete the existing
function myFunction()block. Paste the generated script above. Save with⌘S. - 3Run installFormWebhook onceAt the top of the editor, choose
installFormWebhookfrom the function dropdown and click Run. That creates the form-submit trigger so you can't pick the wrong event source by mistake. - 4Authorise when Google asksClick Advanced → Go to ... (unsafe) → Allow. The warning shows because the script is unverified, it's yours, and you can read every line.
- 5Submit a test responseOpen your Google Form and fill it in. If TEST_MODE is on, check the Apps Script Executions log to see the payload. If TEST_MODE is off, check your endpoint logs.
And when RouteForms makes more sense
A webhook bridge is a one-channel firehose. That's often exactly what you want. Where it gets painful:
- You want different responses going to different endpoints. The script points at one URL. RouteForms evaluates IF-THEN rules per submission and posts to the matching destination, no code change required.
- You need retries on failure.UrlFetchApp doesn't know that the endpoint returned 502. RouteForms retries transient failures with exponential backoff and emails you (paid plans) when a streak starts.
- You need a delivery log a non-engineer can read. The Apps Script Executions log is fine for you, useless for a client. RouteForms gives you a per-form log with the payload, the destination, and the response.
- You need idempotency.Apps Script's own retry logic can produce duplicate POSTs on transient errors. RouteForms enforces a partial unique index on the response ID, so a retry path can never double-post.
Frequently asked questions
What is a Google Forms webhook?▾
It's an HTTPS endpoint that receives a POST request every time someone submits your Google Form. Google Forms doesn't ship webhooks natively — you wire one up by attaching a small Google Apps Script to the form with an onFormSubmit trigger. This tool generates that script for you so the form posts to the URL you pick, in the shape you pick.
Which payload shape should I pick?▾
Three real options. (1) Flat object — one key per question. Simplest, works for custom Node/Python servers and most third-party intake URLs. (2) Apps Script event shape — { namedValues: { Question: [answer] } }, useful when you're testing Apps Script code locally. (3) Flat + _meta block — the flat object plus a _meta sub-object with formId, responseId, and submittedAt. Recommended: it's safe to retry and deduplicate (responseId is the natural dedupe key), and most observability tools handle the extra block cleanly.
Is this the same as the Google Forms to Slack Generator?▾
Different framing for a different use case. The Slack generator detects Slack incoming-webhook URLs and emits Block Kit messages with section/divider blocks. This generator emits raw JSON POSTs in your chosen shape, no Slack-specific formatting. Pick the Slack tool when the destination is a Slack channel. Pick this one when the destination is a custom server, a CRM intake URL, a Make/Zapier webhook, n8n, or anything that wants generic JSON.
Does the script handle authentication?▾
Yes, optionally. Paste a bearer token and the generated script sends Authorization: Bearer <token> on every POST. Token stays in your browser — it's only baked into the script you download. For more elaborate auth (HMAC signatures, OAuth), you'd extend the UrlFetchApp.fetch call manually; the script's options object is a good place to add headers.
What does the script look like end-to-end?▾
Three functions. installFormWebhook() — run once to install the onFormSubmit trigger. collect*() — picks the payload shape you chose and returns the data object. onFormSubmit(e) — fires on every submission, builds the payload, POSTs it (or logs it in test mode). The whole script is under 80 lines, no external dependencies, runs entirely inside Google Apps Script.
What does TEST_MODE do?▾
Replaces the outbound UrlFetchApp.fetch call with Logger.log so you can see the exact payload that would have been sent. Submit a test form response, open the Apps Script Executions log, expand the run, read the JSON. Flip TEST_MODE to false in the script when you're ready to go live.
What about retries and a delivery log?▾
This script tries once and logs non-2xx responses to the Apps Script console. There's no automatic retry, no dedupe, no audit UI. If you need delivery logs, retry-on-failure, and idempotency on the response ID, that's what RouteForms does on top of a script like this one. Free for 30 responses a month.
Why won't Apps Script let me trigger onFormSubmit?▾
Three common reasons. (1) You picked the wrong event source — it needs to be 'From form', not 'From spreadsheet'. (2) You authorised the script but not for form-submit scope. Re-run installFormWebhook and click through the auth prompt. (3) The form is in a different account than the script editor. Open the script editor from inside the form (three-dot menu → Apps Script), not from script.google.com.
Need routing, retries, and a delivery log on top?
The free generator gets you to one endpoint. RouteForms adds the management layer — free for 30 responses a month.
Keep reading
Same idea but Slack-flavoured — detects hooks.slack.com URLs and emits Block Kit messages instead of generic JSON.
If you want the trigger code without the webhook layer, this one lets you pick the output (Slack or generic) up front.
Sample webhook JSON in the same three shapes — useful for testing your endpoint before wiring the form.
Validates that your destination URL actually accepts POSTs and returns a 2xx — saves you a round of debugging.
Long-form walkthrough of the full pipeline with routing rules, delivery logs, and pricing.