No, it should work. I just did a test with a couple of free accounts I have, and they all worked.
- I was able to use the API to pull records from the free app
- Proving you can use the AppSheet API externally with a free account
- I was able to use a webhook step in automation to call an external API
- Proving you can use webhooks in a free account
Here’s my testing setup (so you can copy and try yourself):
- Create a Google Apps Script deployed web app that creates an API you can interact with
- I created one that sends whatever it receives to your email
- Create a scheduled bot in your app with a single task, to send something to your API
If things work, you’ll receive an email from the script.
The Script
First you need to create the API, which sounds more complicated than it actually is. 
steps
Open Apps Script | Google for Developers
Create a new project
Add the following code
Test API deployed web app code
// Set your email here
const MY_EMAIL = 'YOUR_EMAIL'; // CHANGE THIS TO YOUR EMAIL
function doPost(e) {
let debugInfo = {};
let emailSubject = 'Webhook Debug';
let emailBody = '';
try {
const timestamp = new Date().toISOString();
debugInfo.timestamp = timestamp;
debugInfo.receivedEvent = true;
// Capture everything about the request
debugInfo.hasParameter = !!e.parameter;
debugInfo.hasPostData = !!e.postData;
debugInfo.hasParameters = !!e.parameters;
if (e.parameter) {
debugInfo.parameter = e.parameter;
}
if (e.postData) {
debugInfo.postData = {
length: e.postData.length,
type: e.postData.type,
contents: e.postData.contents
};
}
if (e.parameters) {
debugInfo.parameters = e.parameters;
}
// Try to parse body if it exists
let parsedBody = null;
if (e.postData && e.postData.contents) {
try {
parsedBody = JSON.parse(e.postData.contents);
debugInfo.parsedBodySuccess = true;
debugInfo.parsedBody = parsedBody;
} catch (parseError) {
debugInfo.parsedBodySuccess = false;
debugInfo.parseError = parseError.toString();
debugInfo.rawContents = e.postData.contents;
}
}
emailSubject = `Webhook SUCCESS - ${timestamp}`;
emailBody = `Webhook received successfully!\n\nDebug Info:\n${JSON.stringify(debugInfo, null, 2)}`;
// Send success email with fallback methods
try {
// Try multiple email methods
const userEmail = Session.getActiveUser().getEmail();
debugInfo.userEmail = userEmail;
if (!userEmail) {
// Try hardcoded email if session fails
GmailApp.sendEmail(MY_EMAIL, emailSubject, emailBody);
} else {
GmailApp.sendEmail(userEmail, emailSubject, emailBody);
}
debugInfo.emailSent = true;
} catch (emailError) {
debugInfo.emailSent = false;
debugInfo.emailError = emailError.toString();
// Try MailApp as fallback
try {
MailApp.sendEmail(MY_EMAIL, emailSubject, emailBody);
debugInfo.fallbackEmailSent = true;
} catch (fallbackError) {
debugInfo.fallbackEmailError = fallbackError.toString();
}
}
// Return success response
return ContentService
.createTextOutput(JSON.stringify({
status: 'success',
message: 'Webhook processed',
timestamp: timestamp,
debug: debugInfo
}))
.setMimeType(ContentService.MimeType.JSON);
} catch (mainError) {
// Something went wrong - send error email
debugInfo.mainError = mainError.toString();
debugInfo.errorStack = mainError.stack;
try {
GmailApp.sendEmail(
Session.getActiveUser().getEmail(),
'Webhook ERROR',
`Webhook failed!\n\nError: ${mainError.toString()}\n\nStack: ${mainError.stack}\n\nDebug Info:\n${JSON.stringify(debugInfo, null, 2)}`
);
} catch (emailError) {
// Can't even send email
}
return ContentService
.createTextOutput(JSON.stringify({
status: 'error',
message: mainError.toString(),
debug: debugInfo
}))
.setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e) {
try {
GmailApp.sendEmail(
Session.getActiveUser().getEmail(),
'Webhook GET Test',
`GET request received.\n\nParameters: ${JSON.stringify(e.parameter || {}, null, 2)}`
);
return ContentService
.createTextOutput(JSON.stringify({
status: 'success',
message: 'Webhook tester is running',
timestamp: new Date().toISOString()
}))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService
.createTextOutput(JSON.stringify({
status: 'error',
message: error.toString()
}))
.setMimeType(ContentService.MimeType.JSON);
}
}
function testWebhook() {
const testData = {
parameter: {},
postData: {
length: 45,
type: 'application/json',
contents: '{"event":"test","data":{"id":123,"name":"test"}}'
},
parameters: {}
};
const result = doPost(testData);
return `Test completed. Check email for results. Response: ${result.getContent()}`;
}
Save everything, then deploy as a web app
- Deploy button > New Deployment
- Select Type (Top Left Gear) > Web App
- Description: doesn’t matter
- Web app:
- Execute as: ME
- Who has access: ANYONE
Deploy (blue button, bottom right)
Copy the “Web app URL”
Authorize the script by hitting the “Run” button
- This will bring up a bunch of authorizations
- Approve everything so the script can run
The Webhook Task
Next you need to create a test automation in your app, with a webhook task to send some test data to your test API.
steps
Open your app, go to Automation, make a new automation (doesn’t matter what table)
Configure event > Create new event (bottom option)
- Event Name : doesn’t matter
- Event source: scheduled
- the rest of the settings don’t matter
Add a new step
- Type: Call a webhook
- Preset: Custom
- URL: paste the URL on your clipboard (from your API)
- HTTP Verb: Post
- HTTP Content Type: JSON
- Body
{
"Test": "Value",
"Another": "Value"
}
Save everything
Testing
Now that everything is in place, let’s actually test things.
Click the “Run” button for your scheduled automation
- Pro Tip: the reason we make this automation “scheduled” was because then we get the run button, so we can literally now run the automation with the press of a button. Very helpful for testing things.

You should receive an email with the contents.
To debug what’s not working, take a look at the automation monitor, and the audit logs.