---
title: "Webhook to Google Sheets: Transform & Forward Any Payload to a Row | WebhookRelay"
meta:
  "og:description": "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."
  "og:title": "Webhook to Google Sheets: Transform & Forward Any Payload to a Row"
  description: "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."
---

![Stripes](https://webhookrelay.com/blog/webhook-to-google-sheets/images/stripes.svg)

# **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.

![Webhook to Google Sheets: Transform &amp; Forward Any Payload to a Row](https://webhookrelay.com/blog/webhook-to-google-sheets/images/blog/heroes/route.jpg)

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](https://webhookrelay.com) 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](#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](https://webhookrelay.com/blog/webhook-to-google-sheets/features/transform-webhooks/) reshapes each incoming payload into the fields the script writes.

## [Step 1: Create the sheet and the Apps Script](#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](#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](#step-3-create-a-webhook-relay-public-endpoint)

Open [the new public destination page](https://my.webhookrelay.com/new-public-destination) 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](#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](https://webhookrelay.com/blog/webhook-to-google-sheets/features/transform-webhooks/) to reshape it.

Click **Transform** on the input, open the [Functions](https://my.webhookrelay.com/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](#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](#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](https://webhookrelay.com/blog/webhook-to-google-sheets/features/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](https://webhookrelay.com/blog/webhook-to-google-sheets/features/webhook-multiple-destinations/).

The pattern here is identical to the one in [Send a webhook to Discord](https://webhookrelay.com/blog/webhook-to-google-sheets/blog/webhook-to-discord/) — only the destination URL and the body shape change. For another real-world walkthrough, see [TradingView alerts](https://webhookrelay.com/blog/webhook-to-google-sheets/blog/trading-view/).

## [FAQ](#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](https://webhookrelay.com/blog/webhook-to-google-sheets/webhook-bin/) first to inspect the exact body and headers, then write your `doPost` columns and transform around that structure.

## [Get started](#get-started)

[Create a free Webhook Relay account](https://my.webhookrelay.com/register) 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](https://webhookrelay.com/blog/webhook-to-google-sheets/webhook-bin/) first, then write the transform to match.