Sync Shopify Orders to Google Sheets in Real-Time (No Zapier)
Stop paying $200/month for Zapier. Build a real-time Shopify → Google Sheets sync in 30 minutes with webhooks and Apps Script that never breaks.
Zapier charges $200/month for real-time Shopify syncs, and the connection breaks every time Shopify updates their API.
A direct webhook integration using Shopify's native API and Google Apps Script that costs $0 and runs reliably 24/7.
Why Zapier Is Bleeding Your Budget Dry
Every Shopify store owner hits the same wall: you need order data in Google Sheets for inventory, fulfillment, or reporting. Zapier promises “easy integration,” then charges $200+/month once you exceed 750 tasks. Worse, it breaks silently when Shopify updates their API.
This webhook-based integration is production-tested across Shopify stores of all sizes through our Integration service. The typical shop saves $2,400/year by using a direct webhook connection that never breaks.
Unlike brittle Zapier workflows that require constant babysitting, native Shopify webhooks are officially supported by Shopify-they don’t break with API changes.
The Architecture: Shopify Webhooks → Apps Script → Google Sheets
Here’s the production-ready setup we use in our automation implementations:
Flow:
- New order created in Shopify → Shopify sends webhook to your Apps Script endpoint
- Apps Script validates webhook (HMAC signature check)
- Parse order data (line items, customer, shipping, totals)
- Append row to Google Sheets
- Send Slack notification (optional)
Cost: $0 (Google Apps Script is free up to 20,000 executions/day)
Latency: < 2 seconds from order creation to Sheet update
Reliability: 99.9% (backed by Google’s infrastructure)
Step 1: Create the Google Sheet
First, create your order tracking sheet:
- Open Google Sheets and create a new sheet
- Name it “Shopify Orders”
- Add these column headers in row 1:
Order ID | Order Number | Created At | Customer Name | Customer Email | Line Items | Subtotal | Tax | Shipping | Total | Payment Status | Fulfillment Status | Tags | Notes
Pro tip: Keep the column order consistent. Apps Script will write data in this exact order.
Step 2: Set Up the Apps Script Webhook Handler
Now create the webhook receiver:
- In your Google Sheet, click Extensions → Apps Script
- Delete any default code
- Paste this webhook handler:
// webhook-handler.gs
function doPost(e) {
try {
// Validate webhook signature (prevents spoofing)
const isValid = validateShopifyWebhook(e);
if (!isValid) {
return ContentService.createTextOutput('Invalid signature')
.setMimeType(ContentService.MimeType.TEXT);
}
// Parse order data
const order = JSON.parse(e.postData.contents);
// Write to Google Sheets
writeOrderToSheet(order);
// Optional: Send Slack notification
sendSlackNotification(order);
return ContentService.createTextOutput('Success')
.setMimeType(ContentService.MimeType.TEXT);
} catch (error) {
Logger.log('Error processing webhook: ' + error);
return ContentService.createTextOutput('Error: ' + error)
.setMimeType(ContentService.MimeType.TEXT);
}
}
function validateShopifyWebhook(e) {
const SHOPIFY_SECRET = PropertiesService.getScriptProperties().getProperty('SHOPIFY_WEBHOOK_SECRET');
// Get HMAC from header
const hmacHeader = e.parameter['X-Shopify-Hmac-Sha256'] || e.postData.headers['X-Shopify-Hmac-Sha256'];
if (!hmacHeader) return false;
// Calculate expected HMAC
const body = e.postData.contents;
const expectedHmac = Utilities.computeHmacSha256Signature(body, SHOPIFY_SECRET);
const expectedBase64 = Utilities.base64Encode(expectedHmac);
return hmacHeader === expectedBase64;
}
function writeOrderToSheet(order) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Shopify Orders');
// Format line items
const lineItems = order.line_items.map(item =>
`${item.quantity}x ${item.name} ($${item.price})`
).join(' | ');
// Build row data
const rowData = [
order.id,
order.order_number,
order.created_at,
order.customer ? order.customer.first_name + ' ' + order.customer.last_name : 'Guest',
order.customer ? order.customer.email : '',
lineItems,
order.subtotal_price,
order.total_tax,
order.total_shipping_price_set?.shop_money?.amount || '0.00',
order.total_price,
order.financial_status,
order.fulfillment_status || 'unfulfilled',
order.tags,
order.note || ''
];
sheet.appendRow(rowData);
}
function sendSlackNotification(order) {
const SLACK_WEBHOOK = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK');
if (!SLACK_WEBHOOK) return; // Skip if no webhook configured
const message = {
text: `🛍️ New Shopify Order: #${order.order_number}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*New Order #${order.order_number}*\nCustomer: ${order.customer?.email || 'Guest'}\nTotal: $${order.total_price}\n\n_Auto-synced to Google Sheets_`
}
}
]
};
UrlFetchApp.fetch(SLACK_WEBHOOK, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(message)
});
}
- Click Deploy → New deployment
- Select Web app
- Set Execute as to “Me”
- Set Who has access to “Anyone”
- Click Deploy and copy the Web app URL (you’ll need this for Shopify)
Step 3: Store Your Shopify Webhook Secret
You need to securely store your Shopify webhook secret:
- In Apps Script, click Project Settings (gear icon)
- Scroll to Script Properties
- Add a new property:
- Property:
SHOPIFY_WEBHOOK_SECRET - Value: (we’ll get this from Shopify in the next step)
- Property:
Optional: Add SLACK_WEBHOOK property if you want Slack notifications.
Step 4: Create the Shopify Webhook
Now configure Shopify to send order data:
-
Log into your Shopify admin
-
Go to Settings → Notifications
-
Scroll to Webhooks section
-
Click Create webhook
-
Configure:
- Event: Order creation
- Format: JSON
- URL: Paste your Apps Script web app URL
- Webhook API version: Latest (2024-01)
-
Click Save
-
Copy the webhook secret key Shopify generates
-
Go back to your Apps Script → Project Settings → Script Properties
-
Update
SHOPIFY_WEBHOOK_SECRETwith the secret key from Shopify
Step 5: Test the Integration
Let’s verify everything works:
- Create a test order in Shopify:
- Go to Orders → Create order
- Add a product, customer, and complete the order
- Check your Google Sheet within 5 seconds-new row should appear
- Check Apps Script logs (Executions tab) for any errors
Debugging tip: If nothing appears, check:
- Apps Script execution logs for errors
- Shopify webhook delivery attempts (Settings → Notifications → Webhooks → View details)
- HMAC validation (make sure you copied the secret correctly)
Step 6: Add Error Handling & Retry Logic
Production systems need robust error handling:
// error-handling.gs
function writeOrderToSheet(order) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Shopify Orders');
// Check for duplicates (prevent double-writing if webhook retries)
const existingOrders = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();
const orderExists = existingOrders.some(row => row[0] == order.id);
if (orderExists) {
Logger.log(`Order ${order.id} already exists. Skipping.`);
return;
}
// Format line items
const lineItems = order.line_items.map(item =>
`${item.quantity}x ${item.name} ($${item.price})`
).join(' | ');
const rowData = [
order.id,
order.order_number,
order.created_at,
order.customer ? order.customer.first_name + ' ' + order.customer.last_name : 'Guest',
order.customer ? order.customer.email : '',
lineItems,
order.subtotal_price,
order.total_tax,
order.total_shipping_price_set?.shop_money?.amount || '0.00',
order.total_price,
order.financial_status,
order.fulfillment_status || 'unfulfilled',
order.tags,
order.note || ''
];
sheet.appendRow(rowData);
return; // Success, exit retry loop
} catch (error) {
attempt++;
Logger.log(`Attempt ${attempt} failed: ${error}`);
if (attempt >= maxRetries) {
// Send alert after all retries fail
sendErrorAlert(order.id, error);
throw error;
}
Utilities.sleep(1000 * attempt); // Exponential backoff
}
}
}
function sendErrorAlert(orderId, error) {
const ALERT_EMAIL = PropertiesService.getScriptProperties().getProperty('ALERT_EMAIL');
if (!ALERT_EMAIL) return;
MailApp.sendEmail({
to: ALERT_EMAIL,
subject: `⚠️ Shopify Webhook Error: Order ${orderId}`,
body: `Failed to sync order ${orderId} after 3 retries.\n\nError: ${error}\n\nCheck Apps Script logs for details.`
});
}
Need help setting up automated monitoring for your integrations? We can alert you before issues affect customers.
Advanced: Sync Historical Orders (Backfill)
Already have 1,000+ orders in Shopify? Backfill them:
// backfill-orders.gs
function backfillShopifyOrders() {
const SHOPIFY_DOMAIN = PropertiesService.getScriptProperties().getProperty('SHOPIFY_DOMAIN');
const SHOPIFY_API_KEY = PropertiesService.getScriptProperties().getProperty('SHOPIFY_API_KEY');
const SHOPIFY_API_PASSWORD = PropertiesService.getScriptProperties().getProperty('SHOPIFY_API_PASSWORD');
let pageInfo = null;
let hasMore = true;
while (hasMore) {
// Fetch orders (250 per page, Shopify's max)
let url = `https://${SHOPIFY_DOMAIN}/admin/api/2024-01/orders.json?limit=250&status=any`;
if (pageInfo) url += `&page_info=${pageInfo}`;
const options = {
'method': 'get',
'headers': {
'X-Shopify-Access-Token': SHOPIFY_API_PASSWORD
}
};
const response = UrlFetchApp.fetch(url, options);
const data = JSON.parse(response.getContentText());
// Write each order to sheet
data.orders.forEach(order => {
writeOrderToSheet(order);
});
// Check for next page
const linkHeader = response.getHeaders()['Link'];
if (linkHeader && linkHeader.includes('rel="next"')) {
pageInfo = extractPageInfo(linkHeader);
} else {
hasMore = false;
}
Logger.log(`Processed ${data.orders.length} orders. Has more: ${hasMore}`);
Utilities.sleep(500); // Rate limit protection
}
}
function extractPageInfo(linkHeader) {
const nextLink = linkHeader.split(',').find(link => link.includes('rel="next"'));
if (!nextLink) return null;
const match = nextLink.match(/page_info=([^&>]+)/);
return match ? match[1] : null;
}
Run backfillShopifyOrders() once to sync all historical data.
Common Pitfalls (And How We Fix Them)
Pitfall 1: Webhook signature validation fails Make sure you copied the webhook secret (not the API key) from Shopify. The secret is shown once when you create the webhook.
Pitfall 2: Duplicate orders in Sheet Shopify retries webhooks if they timeout. Always check for duplicates before appending (see error handling code above).
Pitfall 3: Line items formatting breaks
Some products have special characters (pipes, commas). Use .join(' | ') to avoid conflicts with CSV exports.
Pitfall 4: Script execution timeout Apps Script has a 30-second execution limit. If you have 100+ line items per order, batch the writes or use a Cloud Function instead.
Production Deployment Checklist
Before going live:
- Test with 10+ orders: Verify all edge cases (multiple line items, discounts, refunds)
- Set up error alerts: Add
ALERT_EMAILto Script Properties - Monitor execution logs: Check daily for the first week
- Add duplicate detection: Use order ID to prevent double-writes
- Document API version: Shopify updates their API regularly-pin your version
- Create a backup: Export your Sheet daily (or use automated backups)
Need Professional Help?
This integration architecture scales from 10 orders/day to 10,000 orders/day. Our Integration service includes:
- Custom field mapping: Sync metafields, custom line item properties, and Shopify Plus features
- Multi-destination sync: Send to Sheets + BigQuery + Slack + your custom backend
- Advanced error handling: Automatic retry logic, dead letter queues, and alerting
- Fulfillment sync: Bi-directional sync (update Shopify when you mark “shipped” in Sheets)
- Performance optimization: Handle 100+ line items per order without timeouts
Most clients save $2,400/year by ditching Zapier and get more reliable data pipelines.
Book a free 30-minute consultation to review your integration needs: Schedule here
Related Services
- Dashboard Analytics - Visualize order data with custom dashboards
- Automation Workflows - Auto-fulfill orders based on inventory rules
- Compare vs Zapier - Why direct integrations beat Zapier
Next Guide
Want to sync Shopify inventory too? Check out our guide on Real-Time Shopify Inventory Sync with Low-Stock Alerts.