Author: By Raj

Part of our Apps Script API Integrations guides. Need this built for your team? Hire a Google Apps Script developer.

Estimated reading time: 10 minutes

Build a Webhook Receiver in Google Apps Script (doPost Guide)

Webhooks turn external events, Stripe payments, Shopify orders, Typeform submissions, into Sheet rows via a deployed Apps Script web app doPost endpoint.

Validate signatures before trusting bodies. Respond within provider timeouts (often 3–5 seconds) by enqueueing heavy work to Script Properties or a Queue sheet for a time-driven processor.

Read /blog/apps-script-doget-dopost-explained for HTTP method nuances and deployment ACLs.

doPost handler skeleton

function doPost(e) { const body = e.postData.contents; const json = JSON.parse(body); ... return ContentService.createTextOutput('ok'); }

Set contentType from e.postData.type when providers send form-encoded payloads.

Signature verification

Stripe: HMAC SHA256 of signed payload. Shopify: X-Shopify-Hmac-Sha256. Compare with Utilities.computeHmacSha256Signature.

Reject mismatches with 401 plain text, do not write bad data.

Queue for slow processing

Append event ID and raw JSON to WebhookQueue sheet in doPost under 2 seconds. Separate processQueue runs every minute.

Mark rows processed to stay idempotent when providers retry webhooks.

Deployment settings

Execute as Me with access Anyone if public internet sends events, understand security tradeoff. IP allowlists are not native; use signature verification.

Use /dev URL only for sandbox; production providers need /exec deployment version.

Example code

function doPost(e) {
  const secret = PropertiesService.getScriptProperties().getProperty('WEBHOOK_SECRET');
  const sig = e.parameter.signature || (e.postData && e.postData.headers && e.postData.headers['X-Signature']);
  const body = e.postData.contents;
  const expected = Utilities.base64Encode(Utilities.computeHmacSha256Signature(body, secret));
  if (sig !== expected) {
    return ContentService.createTextOutput('unauthorized').setMimeType(ContentService.MimeType.TEXT);
  }
  const json = JSON.parse(body);
  SpreadsheetApp.openById('SHEET_ID').getSheetByName('Events').appendRow([new Date(), json.type, JSON.stringify(json)]);
  return ContentService.createTextOutput(JSON.stringify({ received: true })).setMimeType(ContentService.MimeType.JSON);
}
ApproachBest forTradeoff
Apps Script nativeGoogle Workspace-centric workflows6-min limit, quotas
Zapier / MakeNo-code, many connectorsPer-task cost, vendor lock-in
Python + CloudHeavy data / MLHosting cost, separate auth
API integration servicesProduction custom logicBuild cost, you own code

FAQ

Can webhooks call SpreadsheetApp.getActive?

Web apps are standalone, use openById with spreadsheet ID from Properties. Container-bound active spreadsheet is unreliable in doPost.

Why duplicate webhook rows?

Providers retry on non-200 or slow responses. Return 200 quickly and dedupe by event ID column unique constraint logic.

CORS issues?

Browser CORS rarely applies to server-side webhooks. User-facing JS uses doGet/doPost differently, see doGet guide.

Can I test locally?

Use curl -X POST deployment URL with sample JSON. clasp run cannot emulate doPost with full e object easily.

Stripe vs Shopify receivers?

Each has distinct signature headers, never reuse one verifier function without parameterizing algorithm and secret.

Need this done for you? I handle this as part of my consulting work, fixed-price quote within 24 hours.

Book a call with Raj →

Get the full Build a Webhook Receiver in Google Apps Script (doPost Guide) script template

I'll email you a production-ready, commented version you can deploy in 10 minutes.

Need help with this? I handle this as part of my Apps Script API Integrations service.

Shopify, Stripe, Slack, HubSpot, webhooks, and REST API connections.

See how it works →