Push form data to a CRM or external API
Send Northwind form submissions downstream to HubSpot, Notion, or a custom endpoint.
Published Aug 2, 2025
Northwind’s contact form sits on the marketing site, but the team that actually works the leads lives inside the CRM. Copy-pasting each submission across is the sort of job that gets skipped on a busy week — and a missed lead is a missed sale. The form needs to talk to the CRM the moment someone presses submit.
This script wires a Google Form straight to a downstream API. When a response lands, it pulls the fields out of the event object, shapes them into a JSON payload, and posts it to the CRM with a bearer token. Swap the endpoint for HubSpot, Notion, Pipedrive, or any internal service — the contract is the same.
What you’ll need
- A Google Form with at least
EmailandNamequestions, and optionally aCompanyquestion. The script reads them by name from the submit event. - A CRM (or any HTTP endpoint) that accepts a JSON
POSTand returns a 2xx on success. The example uses a bearer-token-authenticated endpoint. - An API key for that endpoint saved as
CRM_KEYin Script Properties — see Store API keys and secrets securely. - A form-submit trigger pointing at
onFormSubmit(set up below).
The script
// The downstream endpoint that accepts the lead. Change this when you swap CRMs.
const CRM_ENDPOINT = 'https://api.crm.example/leads';
// A tag the CRM can use to attribute leads back to this form.
const LEAD_SOURCE = 'northwind-contact-form';
/**
* Form-submit handler. Shapes the response into a CRM lead and posts it.
* Bound to the form's on-submit trigger.
*
* @param {GoogleAppsScript.Events.FormsOnFormSubmit} e
*/
function onFormSubmit(e) {
// 1. Pull the answers out of namedValues. Each value is an array of strings
// because Forms allows multi-select; for single-answer questions we want
// the first entry.
const email = e.namedValues.Email?.[0];
const name = e.namedValues.Name?.[0];
const company = e.namedValues.Company?.[0];
// 2. Guard against the rare case where the form is submitted with no email —
// a CRM lead without one is useless.
if (!email) {
Logger.log('Skipping submission with no email.');
return;
}
// 3. Build the lead payload. Keep keys flat — most CRMs map them straight
// onto contact properties.
const payload = {
email,
name,
company,
source: LEAD_SOURCE,
submittedAt: new Date().toISOString(),
};
// 4. Read the API key from Script Properties. Never paste it into the code.
const key = PropertiesService.getScriptProperties().getProperty('CRM_KEY');
if (!key) {
Logger.log('CRM_KEY is not set — skipping push.');
return;
}
// 5. Post to the CRM. muteHttpExceptions lets us log a useful message
// instead of crashing the trigger on a 4xx.
const res = UrlFetchApp.fetch(CRM_ENDPOINT, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `Bearer ${key}` },
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
const code = res.getResponseCode();
if (code >= 200 && code < 300) {
Logger.log(`Pushed ${email} to CRM (${code}).`);
} else {
Logger.log(`CRM push failed for ${email} — ${code}: ${res.getContentText()}`);
}
}
How it works
- The form-submit trigger fires
onFormSubmitwith an event object whosenamedValuesmap holds each answer keyed by question title. - The script reads
Email,Name, and the optionalCompany, taking the first entry from each array because Forms always returns answers as arrays. - If the email is missing it bails out — a CRM record with no email cannot be matched against future activity, so it is not worth creating.
- It builds a flat JSON payload with the submission timestamp and a fixed
sourcetag so the CRM can filter leads from this form. - The API key is read from Script Properties at call time — it is never in the source. If it is unset the script logs and returns rather than sending an unauthenticated request that would expose the payload anyway.
UrlFetchApp.fetchposts the JSON with a bearer header.muteHttpExceptionskeeps the trigger alive on non-2xx responses; the script logs the status code and body so failures show up in the executions list.
Example run
Suppose someone fills the form with:
| Question | Answer |
|---|---|
| Name | Priya Shah |
| [email protected] | |
| Company | Lumen Studio |
The CRM receives:
{
"email": "[email protected]",
"name": "Priya Shah",
"company": "Lumen Studio",
"source": "northwind-contact-form",
"submittedAt": "2025-08-02T09:14:22.000Z"
}
And the execution log shows Pushed [email protected] to CRM (201).
Trigger it
This runs on form submission, so it needs an installable trigger — the simple
onFormSubmit name alone is not enough when you need authenticated services
like UrlFetchApp.
- In the Apps Script editor, open Triggers (clock icon, left sidebar).
- Click Add trigger and choose
onFormSubmit. - Event source: From form. Event type: On form submit.
- Save and approve the authorisation prompt.
Watch out for
- Question titles are the contract. If someone renames
EmailtoEmail addressin the form,e.namedValues.Emailbecomesundefinedand the script silently skips every submission. Either freeze the titles or read by position withe.values. - Downstream outages will lose leads.
muteHttpExceptionskeeps the trigger green, but a logged failure is not a retry. For anything business-critical, also append the row to a backup sheet so you can replay failed pushes later. - Bearer tokens leak through logs. Never log the
Authorizationheader, and never log the full request options object — only the response code and body. - Apps Script triggers run as the form owner, which means every push uses that one CRM key. Rotate it when the owner changes, and revoke the old one in the CRM admin console.
Related
Trigger an onboarding sequence on form submit
Kick off tasks when a new Northwind hire submits their starter form.
Updated Oct 17, 2025
Build a content-submission queue
Collect Northwind guest posts or ideas for review through a Form.
Updated Oct 9, 2025
Score sentiment in open-text feedback
Rate Northwind feedback comments without manual review — using the in-Sheet sentiment function.
Updated Oct 5, 2025
Build a peer-nomination and voting system
Collect and tally Northwind nominations for awards or initiatives — one ballot, anonymous.
Updated Oct 1, 2025
Roll a form over each cycle
Archive old responses and reset for the next Northwind cycle — quarterly OKR check-ins.
Updated Sep 27, 2025