Introduction
Integrating Google Forms with Salesforce via webhooks enables real-time data transfer without third-party tools. This guide covers the end-to-end implementation, troubleshooting common errors, and best practices.
Section 1: Architecture Overview
Components:
- Google Form: Collects user data
- Google Apps Script: Processes form submissions
- Salesforce REST Endpoint: Receives and stores data
- OAuth 2.0: Secures the connection
Section 2: Step-by-Step Implementation
Step 1: Prepare Salesforce
- Create a REST Endpoint
@RestResource(urlMapping='/form-submissions/*')
global class FormWebhook {
@HttpPost
global static String processFormData() {
try {
// Parse incoming JSON
RestRequest req = RestContext.request;
Map<String, Object> payload = (Map<String, Object>)JSON.deserializeUntyped(req.requestbody.toString());
// Create Lead record
Lead newLead = new Lead(
FirstName = String.valueOf(payload.get('firstName')),
LastName = String.valueOf(payload.get('lastName')),
Email = String.valueOf(payload.get('email')),
Company = String.valueOf(payload.get('company'))
);
insert newLead;
return '{"status": "success", "recordId": "' + newLead.Id + '"}';
} catch(Exception e) {
return '{"error": "' + e.getMessage() + '"}';
}
}
}
2. Enable Remote Site Settings
`
- Go to Setup → Security → Remote Site Settings
- Add https://script.google.com as a trusted domain`
Step 2: Set Up Google Apps Script
- Create the Webhook Handler
function onFormSubmit(e) {
const sfEndpoint = 'https://yourdomain.salesforce.com/services/apexrest/form-submissions';
const accessToken = getSalesforceToken(); // See Step 3 for OAuth setup
const payload = {
firstName: e.namedValues['First Name'][0],
lastName: e.namedValues['Last Name'][0],
email: e.namedValues['Email'][0],
company: e.namedValues['Company'][0]
};
const options = {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(sfEndpoint, options);
const result = JSON.parse(response.getContentText());
if (result.error) {
Logger.log('Error: ' + result.error);
sendErrorEmail(result.error); // Implement error handling
} else {
Logger.log('Success: Record ID ' + result.recordId);
}
} catch (error) {
Logger.log('Connection Error: ' + error.toString());
}
}
2. Configure the Trigger
Open script editor in Google Forms
Go to Edit → Current project's triggers
Set:
Function: onFormSubmit
Event: On form submit
Failure notification: Daily
Section 3: Authentication Methods
Option A: Password-Based OAuth
function getSalesforceToken() {
const loginUrl = 'https://login.salesforce.com/services/oauth2/token';
const params = {
method: 'POST',
payload: {
grant_type: 'password',
client_id: 'YOUR_CONSUMER_KEY',
client_secret: 'YOUR_CONSUMER_SECRET',
username: 'salesforce@email.com',
password: 'PASSWORD+SECURITY_TOKEN'
}
};
const response = UrlFetchApp.fetch(loginUrl, params);
return JSON.parse(response).access_token;
}
*Option B: JWT Bearer Flow (Recommended for Production)
*
- Generate a self-signed certificate:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out cert.pem
- Upload cert.pem to Salesforce:
Setup → Certificate and Key Management
- Use this Apps Script function:
function getJWTAccessToken() {
const jwtHeader = Utilities.base64Encode(JSON.stringify({
"alg": "RS256",
"typ": "JWT"
}));
const jwtClaim = Utilities.base64Encode(JSON.stringify({
"iss": "YOUR_CONSUMER_KEY",
"sub": "user@domain.com",
"aud": "https://login.salesforce.com",
"exp": Math.floor(Date.now()/1000) + 300
}));
const signature = Utilities.computeRsaSha256Signature(
jwtHeader + "." + jwtClaim,
"-----BEGIN PRIVATE KEY-----\n" +
"YOUR_PRIVATE_KEY\n" +
"-----END PRIVATE KEY-----"
);
const jwt = jwtHeader + "." + jwtClaim + "." + Utilities.base64Encode(signature);
const response = UrlFetchApp.fetch('https://login.salesforce.com/services/oauth2/token', {
method: 'POST',
payload: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwt
}
});
return JSON.parse(response).access_token;
}
Section 4: Testing & Debugging
Test Cases
Top comments (0)