Apps Script example · 6 min read

Send Sheet Row Data to an External Webhook: Copy-Paste Apps Script Pattern

Working send sheet row data to an external webhook example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

SheetsUrlFetchAppWebhooks

Plenty of external tools accept incoming data through a webhook URL, and once you have data landing in a spreadsheet from some other automation, pushing it onward to that webhook is a natural next step.

This script scans a sheet for rows not yet marked as delivered, builds a JSON payload from each row's values, posts it to a configured webhook URL with UrlFetchApp, and records the delivery status back in the sheet.

Treating the delivery status column as the source of truth for what still needs sending means the function can be run repeatedly, including on a time-driven trigger, without resending rows that already succeeded.

The same posting logic works for almost any webhook-accepting service, since the only thing that changes between integrations is the shape of the JSON payload and the target URL.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

Outbound sheet columnLetterRole
Payload JSONABody posted to webhook
EndpointBTarget URL
DeliveredCTimestamp after 2xx

What It Does

The script loops over sheet rows below the header, skips any row already marked Delivered, builds a JSON object keyed by column header for the remaining rows, and posts that object to a webhook URL with UrlFetchApp.fetch.

After each successful post, confirmed by checking the response code, the script writes Delivered into that row's status column so subsequent runs skip it automatically.

Prerequisites

You need the destination webhook URL from the receiving service, along with any required authentication header such as an API key or bearer token that service expects on incoming requests.

Add a Delivered status column to your sheet if it does not already exist, since the send function relies on that column both to decide what to send and to record what already succeeded.

Walkthrough

Set WEBHOOK_URL and any AUTH_HEADER value at the top of the script, then paste in the sendRowsToWebhook function and adjust the column list to match your sheet's headers.

Add one test row with clearly fake but structurally realistic data, run the function manually, and check the receiving service's logs or a tool like webhook.site to confirm the JSON payload arrived correctly.

Once a manual run behaves correctly, add a time-driven trigger so new rows are pushed out automatically shortly after they are added to the sheet.

Edge Cases

A webhook endpoint that is temporarily down returns a non-200 response, and in that case the script deliberately leaves the status column blank so the row will be retried automatically on the next run rather than being marked as delivered.

Rows added in the middle of the sheet rather than appended at the bottom are still picked up correctly, since the script scans the whole data range by status column rather than assuming new rows only appear at the end.

Testing

Point WEBHOOK_URL at a temporary testing endpoint like webhook.site, send two test rows, and confirm both payloads appear there with the correct field names and values.

Temporarily point WEBHOOK_URL at an invalid address, run the function, and confirm the affected row's status column stays blank rather than being incorrectly marked Delivered.

Hardening

Add a retry counter column so rows that fail repeatedly after several attempts get flagged for manual review instead of being retried forever on every trigger run.

Sign the outgoing payload with an HMAC using a shared secret stored in Script Properties if the receiving service supports signature verification, which lets it confirm the request genuinely came from your script.

Variations

Batch several unsent rows into a single array payload instead of one request per row when the receiving service supports bulk ingestion and you want to reduce the number of outbound requests.

Combine this script with the doPost receiver tutorial to build a two-way integration where your sheet both sends new rows out and receives status updates back through its own webhook endpoint.

sendRowsToWebhook.gs

sendRowsToWebhook loops over undelivered sheet rows, builds a JSON payload from the header row, posts it with UrlFetchApp, and marks the row Delivered on a 200 response.

// Post new sheet rows to an external webhook as JSON
function sendRowsToWebhook() {
  var SHEET_NAME = 'Outbox';
  var WEBHOOK_URL = 'https://example.com/incoming-webhook';
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  var data = sheet.getDataRange().getValues();
  var headers = data[0];
  var statusCol = headers.indexOf('Delivered');

  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    if (row[statusCol] === 'Delivered') continue;

    var payload = {};
    for (var c = 0; c < headers.length; c++) {
      if (c !== statusCol) payload[headers[c]] = row[c];
    }

    var response = UrlFetchApp.fetch(WEBHOOK_URL, {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify(payload),
      muteHttpExceptions: true
    });

    if (response.getResponseCode() === 200) {
      sheet.getRange(i + 1, statusCol + 1).setValue('Delivered');
    }
  }
}
  1. Line 8: Looking up the Delivered column index once up front means the rest of the function can refer to statusCol instead of repeating indexOf calls.
  2. Line 12: Skipping any row already marked Delivered is what makes it safe to run this function repeatedly, including on a recurring trigger.
  3. Line 16: Excluding the status column itself from the payload keeps the outgoing JSON limited to the actual data fields the receiving service expects.
  4. Line 22: Serializing the payload object with JSON.stringify is required because UrlFetchApp expects the request body as a string, not a raw object.
  5. Line 26: Only marking a row Delivered after seeing a 200 response ensures a failed request leaves the row eligible for an automatic retry next run.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before you run: webhook send urlFetch

  • 1Destination webhook URL obtained from the receiving service
  • 2Any required authentication header identified and configured
  • 3Delivered status column added to the source sheet
  • 4WEBHOOK_URL and column list adjusted at the top of the script
  • 5Test row sent and verified on the receiving end
  • 6Failed delivery tested to confirm the row is not marked Delivered

Frequently asked questions

The status column is deliberately left blank so the row is retried automatically on the next run instead of being marked as delivered.

Yes, batch unsent rows into a single array payload if the receiving service supports bulk ingestion, as noted in the variations section.

Point WEBHOOK_URL at a temporary testing endpoint like webhook.site to inspect exactly what was sent.

Yes, the script scans the whole data range by status column rather than assuming new rows only appear at the bottom.

Yes, sign it with an HMAC using a shared secret stored in Script Properties if the receiving service supports signature verification.

Add a retry counter column so rows failing after several attempts get flagged for manual review instead of retrying forever, as described in the hardening section.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.