
Magento to Google Sheets Integration: Real-Time Order Sync with Apps Script
You can connect Magento 2 (Adobe Commerce) to Google Sheets in an afternoon, with no middleware and no monthly connector fee. Magento fires a webhook the moment an order is placed; a Google Apps Script web app catches it and appends the row. Orders appear in the sheet within about a second of checkout, and the whole pipeline runs inside your own Google account.
This guide builds that end to end — the Apps Script side, the Magento side, and the one production mistake that will slow your checkout down if you ship the simple version of it.
Most teams reach this page after doing it the two expensive ways: manual CSV exports at the end of each day, or a middleware connector charging a monthly fee to poll an API and still arrive minutes late. Webhooks remove both. If you need a different slice of the same integration — products, prices, or pushing data back into Magento — there is a map at the end of this post.
The Google Workspace half of this builds on patterns we cover separately: building interfaces on Sheets, handling auth, and receiving webhooks in Apps Script.
The Architecture: Magento Webhook to Apps Script
We are going to use the doPost() architecture we discussed in our Apps Script Webhooks guide. The flow is simple:
- A customer clicks "Place Order" in Magento.
- Magento fires an internal event (
sales_order_save_commit_after). - Your Magento server sends a lightweight HTTP POST payload directly to a Google Apps Script URL.
- Google Apps Script parses the JSON and appends the order to a Google Sheet.
Notice what is missing here? No middleware. No API polling. No delays. It happens instantly.
Step 1: Prepping the Google Sheet Backend
First, let's create the destination. Open a new Google Sheet, name the first tab LiveOrders, and create the following headers in Row 1:
| Magento Order ID | Status | Grand Total | Customer Email |
|---|
Next, open Extensions > Apps Script and deploy the listener. We'll add some basic security here to ensure random bots don't fill your sheet with garbage data.
// Code.gs
// Define a secret key that only Magento and Apps Script know
const WEBHOOK_SECRET = "MageSheet_Super_Secret_2026";
function doPost(e) {
try {
const payload = JSON.parse(e.postData.contents);
// Security check! Did Magento send the correct secret?
if (payload.secret !== WEBHOOK_SECRET) {
return ContentService.createTextOutput("Forbidden: Invalid Secret").setStatusCode(403);
}
// Parse the order data
const order = payload.order_data;
// Select the sheet and append
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("LiveOrders");
sheet.appendRow([
order.increment_id,
order.status,
order.grand_total,
order.customer_email
]);
return ContentService.createTextOutput(JSON.stringify({"status": "success"}))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService.createTextOutput("Error: " + error.message).setStatusCode(500);
}
}
Deploy this as a Web App (Execute as: Me, Access: Anyone). Copy the resulting URL.
Step 2: Firing the Event from Magento 2
While Magento 2 has robust REST and GraphQL APIs for pulling data, native "Outbound Webhooks" often require a small custom module.
To keep this tutorial lightweight, we will create a simple Observer module in Magento. Let's assume you have a custom module named MageSheet_GoogleSync.
You need an events.xml file in app/code/MageSheet/GoogleSync/etc/events.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="sales_order_place_after">
<observer name="magesheet_sync_order_to_google" instance="MageSheet\GoogleSync\Observer\SyncOrder" />
</event>
</config>
And your Observer file app/code/MageSheet/GoogleSync/Observer/SyncOrder.php:
<?php
namespace MageSheet\GoogleSync\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\HTTP\Client\Curl;
class SyncOrder implements ObserverInterface
{
private $curl;
public function __construct(Curl $curl)
{
$this->curl = $curl;
}
public function execute(\Magento\Framework\Event\Observer $observer)
{
$order = $observer->getEvent()->getOrder();
// The URL you got from Google Apps Script
$googleAppUrl = "https://script.google.com/macros/s/AKfycby..._aBCD/exec";
// Prepare payload matching our Apps Script expectations
$payload = [
"secret" => "MageSheet_Super_Secret_2026",
"order_data" => [
"increment_id" => $order->getIncrementId(),
"status" => $order->getStatus(),
"grand_total" => $order->getGrandTotal(),
"customer_email" => $order->getCustomerEmail()
]
];
// Fire the cURL POST request
$this->curl->addHeader("Content-Type", "application/json");
$this->curl->post($googleAppUrl, json_encode($payload));
// Important: Use asynchronous queues (RabbitMQ/Cron) in production
// to prevent Google's response time from slowing down Magento checkout!
}
}
The Crucial Production Warning: Asynchronous Queues
In the PHP example above, we placed a direct cURL request inside the sales_order_place_after event.
Warning: While this is great for a development test, doing this synchronously in production means your customer's browser will keep spinning on the checkout page until Google Apps Script replies. If Google is slow, your checkout is slow.
For enterprise environments, you MUST wrap that cURL request in a Magento Message Queue (RabbitMQ) or process it via a Cron job. This ensures that Magento instantly tells the customer "Order Success", while the actual HTTP request to Google happens a few seconds later in the background.
The Power of Instant Visibility
By utilizing this architecture, your Ops team can open Google Sheets and watch orders arrive the moment a B2B transaction completes. From there, you can use Sheets formulas, Looker Studio dashboards, or Google Apps Script triggers to automatically forward that order to a 3PL or accounting department.
Backfilling the Orders You Already Have
Webhooks only deliver what happens next. On day one your sheet is empty, and most teams want their existing orders in there too. That is a separate job, done once, against Magento's REST API rather than the webhook.
Create an integration in the Magento admin (System → Extensions → Integrations), grant it read access to Sales, activate it, and copy the access token. Then page through the orders endpoint:
function backfillOrders() {
const props = PropertiesService.getScriptProperties();
const base = props.getProperty('MAGENTO_BASE'); // https://yourstore.com
const token = props.getProperty('MAGENTO_TOKEN');
const since = props.getProperty('BACKFILL_CURSOR') || '2026-01-01 00:00:00';
const rows = [];
let page = 1;
while (page <= 40) { // hard stop, inside 6 minutes
const q = [
'searchCriteria[filter_groups][0][filters][0][field]=created_at',
'searchCriteria[filter_groups][0][filters][0][value]=' + encodeURIComponent(since),
'searchCriteria[filter_groups][0][filters][0][condition_type]=gt',
'searchCriteria[sortOrders][0][field]=created_at',
'searchCriteria[sortOrders][0][direction]=ASC',
'searchCriteria[pageSize]=100',
'searchCriteria[currentPage]=' + page,
].join('&');
const res = UrlFetchApp.fetch(base + '/rest/V1/orders?' + q, {
headers: { Authorization: 'Bearer ' + token },
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) {
throw new Error('Magento ' + res.getResponseCode() + ': ' +
res.getContentText().slice(0, 300));
}
const items = JSON.parse(res.getContentText()).items || [];
items.forEach(o => rows.push([
o.increment_id, o.created_at, o.status,
o.customer_email, Number(o.grand_total), o.order_currency_code,
]));
if (items.length < 100) break;
page++;
}
if (!rows.length) return;
const sheet = SpreadsheetApp.getActive().getSheetByName('Orders');
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
props.setProperty('BACKFILL_CURSOR', rows[rows.length - 1][1]); // resume point
}
Three details make this survive a real catalogue. Store a cursor so a run that hits the six-minute ceiling resumes instead of restarting — see running long jobs past that limit. Write once with setValues, never appendRow in a loop. And grant read-only access: a reporting integration has no business holding write permissions on your store.
Run it once to catch up, then let the webhook keep the sheet current from there.
Which Part of the Integration Do You Need?
"Magento to Google Sheets" covers several different jobs. This post is the order-sync one. Here is where the others live:
| What you want | Direction | Where it is covered |
|---|---|---|
| New orders arriving live | Magento → Sheets | This post |
| Historical orders | Magento → Sheets | The backfill section above |
| Line items for fulfilment | Magento → Sheets | Order line item exports |
| Products, prices, catalogue | Magento → Sheets | Omnichannel PIM with Google Workspace |
| Customers and companies | Magento → Sheets | A free B2B CRM in Google Workspace |
| Bulk updates back into Magento | Sheets → Magento | Triggering Magento REST APIs from Sheets |
| Invoices from synced orders | Sheets → Docs/PDF | Automated B2B invoices |
| Dashboards on the synced data | Sheets → BI | Looker Studio vs Apps Script dashboards |
If you want the whole picture rather than one piece, the complete Magento and Google Sheets automation playbook puts these in order.
What This Replaces
A middleware connector for Magento-to-Sheets runs from roughly $30 to well over $100 a month, polls on a schedule so your data is always a few minutes stale, and meters by task, so the bill grows with your order volume. The pipeline above costs nothing to run, arrives in about a second, and lives in your own Google account against your own integration token. We do the same arithmetic across automation platforms generally in rent or own your automation.
The parts that get genuinely hard are not in the code above: message queues so checkout never waits on Google, HMAC verification on the webhook endpoint, replay of dropped events, and staying inside Apps Script's quotas at volume. If you would rather have that built once and handed over working against your real order volume, tell us what your store needs to see in the sheet. The feasibility review is free — and if a connector genuinely covers your case for less than the build, we will say so.
Frequently Asked Questions
Why use webhooks instead of Magento's REST API polling for order sync?
Polling wastes cycles and adds latency. If you poll every 5 minutes, the average order takes 2.5 minutes to show up in your Sheet. Webhooks deliver in under 500ms because Magento pushes the event the instant it fires. You also eliminate API rate-limit risk — polling 12 times an hour across multiple endpoints burns through Magento's default throttle on busy stores. Webhooks are the correct production pattern.
What happens when Google Apps Script is down or returns an error?
Magento's webhook subscriber will retry with exponential backoff, but only for a limited number of attempts before dropping the event. The production-safe pattern is to have your Apps Script endpoint respond 200 OK immediately after appending to a raw intake sheet, then process the event in a background trigger. This decouples delivery from processing — if your enrichment logic breaks, you still have the raw event and can replay it later.
Does this work on Magento Open Source, Adobe Commerce Cloud, and on-prem equally?
Yes for all three, with small config differences. Open Source and on-prem need a custom module to emit the webhook (or an extension like Mageplaza Webhook). Adobe Commerce Cloud supports webhooks natively via the Extensibility framework in 2.4.6+. The Apps Script endpoint is identical in all three cases — what changes is only the Magento-side wiring.
How do I protect the webhook endpoint from being called by anyone with the URL?
Two layers: (1) Magento signs the webhook payload with an HMAC shared secret, and your doPost() function rejects any request whose signature does not match. (2) The Apps Script deployment uses 'Execute as: Me' plus a URL path secret so the endpoint is non-guessable. Never rely on the URL being secret alone — scanners find Apps Script webhook URLs within hours of deployment.
Is there a limit to how many orders per hour this pipeline can handle?
Apps Script has a 6-minute execution ceiling and roughly 90-second quota per individual URL fetch. In practice the sync handles 2,000-3,000 orders per hour comfortably. Past that, the pattern is to have the webhook write raw events into Google Sheets only, and then pull them into BigQuery on a schedule for heavy analytics — we cover that handoff in our Looker Studio article.



