Require Shared Secret

A public /exec URL is easy to leak, so your doPost shouldn't trust whoever calls it. requireSharedSecret(e, propertyKey) reads the expected secret from Script Properties, compares it with ?secret= on the URL (or a secret field in a JSON body), and returns { ok, reason }. Your doPost checks it first and returns an error before writing anything.

One honest limitation: Apps Script's doPost(e) event doesn't give you the request headers, so an X-Webhook-Secret header can't be checked here. Put the secret in the query string or the body.

When to use

  • Webhook receivers (Stripe-style, Typeform, Zapier, your own scripts)
  • Small JSON APIs you expose to a partner or another script
  • Any web app where “anyone with the link” is too generous

How to use this snippet

/** * Check a shared secret on a web app request. * Accepts ?secret=... (e.parameter.secret) or a "secret" field in a JSON body. * doPost(e) has no request headers, so header secrets aren't supported. * * @param {Object} e the doPost / doGet event * @param {string=} propertyKey Script Property holding the secret (default WEBHOOK_SECRET) * @return {{ok: boolean, reason?: string}} */ function requireSharedSecret(e, propertyKey) { var expected = PropertiesService.getScriptProperties() .getProperty(propertyKey || 'WEBHOOK_SECRET'); if (!expected) return { ok: false, reason: 'Server secret not configured' }; var got = (e && e.parameter && e.parameter.secret) || ''; if (!got && e && e.postData && /json/i.test(e.postData.type || '')) { try { got = JSON.parse(e.postData.contents).secret || ''; } catch (x) {} } if (!got) return { ok: false, reason: 'Missing secret' }; return got === expected ? { ok: true } : { ok: false, reason: 'Bad secret' }; }

Example

function doPost(e) { var auth = requireSharedSecret(e, 'WEBHOOK_SECRET'); if (!auth.ok) return jsonErr(auth.reason, 401); // nothing written var payload = JSON.parse(e.postData.contents); delete payload.secret; // don't store it SpreadsheetApp.getActive().getSheetByName('Inbox') .appendRow([new Date(), JSON.stringify(payload)]); return jsonOk({ received: true }); }

jsonOk / jsonErr come from the JSON OK / ERR helpers. Apps Script still answers HTTP 200, so the caller sees the error in the body.

Tips:

  • Use a long random secret (32+ characters) and rotate it by changing the Script Property, not the code.
  • Never log e.parameter or the raw body as-is: the secret is in there. Mask it first (mask-secret).
  • A query-string secret can end up in the sender's logs. If the sender supports a JSON body field, prefer that.

Tip: NitroGAS Co-Pilot can add the secret check to the top of an existing doPost and move the secret into Script Properties. Paste your doPost.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension