Webhook to Google Sheets: Transform & Forward Any Payload to a Row

Send a webhook to Google Sheets. Step-by-step guide to forward an incoming webhook into a spreadsheet using an Apps Script Web App, transforming the payload in flight.

You have a service that fires webhooks — a contact form, a payment provider, a sign-up flow, a CI pipeline, a monitoring tool — and you want each event logged as a row in Google Sheets. The problem: Sheets has no native webhook endpoint, and even when you build one, the webhook your source sends almost never matches the columns you want.

Webhook Relay sits in the middle. It receives the incoming webhook at a stable public URL, transforms the payload into the shape your spreadsheet bridge expects, and delivers it — no glue server, no Lambda, no maintenance.

How it works

The flow is a single hop with a transform in the middle:

Your service  ──▶  Webhook Relay  ──▶  transform to { ...fields }  ──▶  Apps Script Web App  ──▶  new row
 (raw webhook)      (public URL)        (serverless function)           (doPost endpoint)         (in your sheet)

Google Sheets can't receive a webhook on its own. The clean, no-OAuth way to get data in is a Google Apps Script Web App: a doPost(e) function that runs in the context of your sheet, appends a row, and is published at a public URL. That URL becomes the Webhook Relay destination, and a small transformation function reshapes each incoming payload into the fields the script writes.

Step 1: Create the sheet and the Apps Script

Create a new Google Sheet. Then open Extensions → Apps Script and replace the default code with a doPost handler that parses the incoming JSON and appends a row:

function doPost(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  const data = JSON.parse(e.postData.contents);

  sheet.appendRow([
    new Date(),
    data.event || "",
    data.message || "",
  ]);

  return ContentService
    .createTextOutput(JSON.stringify({ status: "ok" }))
    .setMimeType(ContentService.MimeType.JSON);
}

This appends a row with a timestamp plus whatever event and message fields arrive in the JSON body. Adjust the columns to whatever you want to capture.

Step 2: Deploy it as a Web App

In the Apps Script editor, click Deploy → New deployment, choose type Web app, and set:

  • Execute as: Me
  • Who has access: Anyone

Click Deploy, authorize the script when prompted, and copy the Web app URL. It looks like:

https://script.google.com/macros/s/AKfyc.../exec

That public URL is the destination Webhook Relay will deliver to. Note: every time you change the script, you must publish a new version of the deployment for the change to take effect.

Step 3: Create a Webhook Relay public endpoint

Open the new public destination page and paste the Apps Script Web app URL as the destination. Webhook Relay gives you back an input URL — a public endpoint that you'll point your source service at. Everything sent to it gets forwarded toward your sheet.

Step 4: Add a transformation function

If your source already sends JSON with exactly the fields your doPost reads, you can forward it untouched. More often the incoming payload has different field names, so add a transformation to reshape it.

Click Transform on the input, open the Functions page, create a function "from scratch", and paste this Lua:

local json = require("json")

-- incoming payload from your source service
local incoming = json.decode(r.RequestBody)

-- reshape into the fields the Apps Script expects
local payload = {
    event = incoming.type,
    message = incoming.summary,
}

local body, err = json.encode(payload)
if err then error(err) end

r:SetRequestHeader("content-type", "application/json")
r:SetRequestMethod("POST")
r:SetRequestBody(body)

This decodes the raw incoming body, maps its fields onto the event and message keys the script writes, sets the right headers and method, and lets Webhook Relay deliver it. The Web App appends the row.

If you don't need to reshape anything, you can pass the body straight through instead:

r:SetRequestHeader("content-type", "application/json")
r:SetRequestMethod("POST")
r:SetRequestBody(r.RequestBody)

Step 5: Point your source at the URL and test

Configure your source service (the form, payment provider, monitor — whatever you're integrating) to send its webhook to the Webhook Relay input URL.

To confirm everything works before wiring up the real service, fire a quick test with curl:

curl -X POST https://my.webhookrelay.com/v1/webhooks/your-input-id \
  -H 'content-type: application/json' \
  -d '{"type":"signup","summary":"New trial: acme.com"}'

Within a second or two, a new row — timestamp, signup, New trial: acme.com — appears in your sheet. If nothing shows up, open the request log in your dashboard to see exactly what was received and delivered.

Going further

Once the basic bridge works, the transform function is where the real power is.

Capture more columns. Add fields in both the Apps Script appendRow([...]) call and the Lua payload table — amounts, emails, IDs, statuses — to build a full audit log from your webhooks.

Enrich the row before writing. A transform function can call any HTTP API, so you can look up extra data (a customer name, a geolocation) and add it as a column before the row is appended.

Filter out noise. Not every event deserves a row. Use forwarding rules to only deliver events that match a condition, or drop them in the function with an early return.

Fan out. Add more destinations so one incoming webhook lands in a sheet and a chat at the same time. See forwarding to multiple destinations.

The pattern here is identical to the one in Send a webhook to Discord — only the destination URL and the body shape change. For another real-world walkthrough, see TradingView alerts.

FAQ

Why an Apps Script Web App instead of the Sheets API? Because it needs no OAuth setup on your side. The Web App runs as you, has direct access to the spreadsheet, and exposes a single public URL that Webhook Relay can POST to.

Do I need to run a server? No. Webhook Relay receives, transforms, and delivers everything in the cloud, and the Apps Script runs on Google's infrastructure. There's nothing to host or keep online.

How do I see the raw payload my source sends? Send it to a free Webhook Bin first to inspect the exact body and headers, then write your doPost columns and transform around that structure.

Get started

Create a free Webhook Relay account and set up your first "anything → Google Sheets" bridge in a few minutes. Not sure what your source sends? Inspect the payload in a Webhook Bin first, then write the transform to match.