I've built this workflow — or a version of it — for multiple clients. A beauty brand doing 400 orders/month was manually exporting a CSV every morning, pasting it into a Google Sheet, and colour-coding it by fulfilment status. About 45 minutes, every day, by their operations manager.
We replaced it with an n8n workflow. It now runs automatically on every new Shopify order, appends a row to their sheet within seconds, and hasn't needed manual intervention in months.
This tutorial walks you through building exactly that workflow. I'll be specific: n8n version, exact node configs, field mapping, error handling. I'll also cover the gotchas that other guides skip.
What You're Building
A real-time Shopify → Google Sheets automation that:
- Triggers instantly when a new order is placed in Shopify
- Extracts order data (order number, customer, items, value, status)
- Appends a new row to your Google Sheet
- Handles errors gracefully (doesn't silently fail)
n8n version: This guide was written using n8n v1.x (specifically tested on v1.35+). The node names and UI match the current self-hosted and n8n Cloud versions.
Time to build: 20–30 minutes if you've used n8n before. 45–60 minutes if you're new to it.
Prerequisites
- n8n running (self-hosted on a VPS or via n8n Cloud)
- Shopify store with API access (any plan)
- Google Sheet prepared with headers
- Google account connected in n8n (we'll use OAuth)
Step 1: Set Up Your Google Sheet
Before touching n8n, create the destination sheet. This avoids confusion when mapping fields later.
Create a new Google Sheet with these column headers in Row 1:
Order ID | Order Number | Date | Customer Name | Email | Phone | Items | Total | Payment Status | Fulfilment Status | City | State | Country
You can add or remove columns based on what your team actually uses. Keep it to what gets acted on — a 30-column sheet that nobody reads is worse than a clean 10-column one.
Important: Note the Sheet ID from the URL (/spreadsheets/d/[SHEET_ID]/) and the tab name. You'll need both in n8n.
Step 2: Create the Shopify Webhook in n8n
Open n8n and create a new workflow.
Add the Shopify Trigger Node
Search for "Shopify Trigger" in the node panel. This is n8n's native Shopify integration — it handles webhook registration automatically.
Configuration:
- Event:
Order Created - Authentication: You'll need to create a Shopify credential. Use a private app API key (Settings → Apps → Develop apps → Create app → Configure Admin API with
read_ordersscope).
HTTP Request vs Shopify Trigger node: my preference
You can also receive Shopify webhooks via n8n's generic Webhook node (HTTP Request receiver) and register the webhook URL manually in Shopify. Some developers prefer this for control.
I use the Shopify Trigger node for almost all client work because:
- It auto-registers the webhook in Shopify (no manual setup)
- It handles Shopify's HMAC signature verification automatically
- It validates the payload before passing it downstream
The only time I use the raw Webhook node is when I need to receive Shopify webhooks in a specific order or chain them with other external triggers. For this use case, the Shopify Trigger node is the right call.
Activate and Test the Trigger
Click "Listen for Test Event" in the Shopify Trigger node, then place a test order in Shopify (or use an existing order from your Shopify admin → Orders → click any order → Actions → "Resend Webhooks" if available on your plan).
n8n will receive the webhook payload and display it. This is your raw order data — examine it before building the rest.
Step 3: Map the Fields with a Set Node
Before writing to Google Sheets, add a Set node to clean and flatten the Shopify order payload. Shopify's order object is deeply nested — customer data is nested, line items are an array, address is nested under shipping_address. Flattening it here makes the Sheets node simpler.
Add a Set node after the trigger and configure these values:
orderId = {{ $json.id }}
orderNumber = {{ $json.order_number }}
orderDate = {{ $json.created_at }}
customerName = {{ $json.customer.first_name }} {{ $json.customer.last_name }}
customerEmail = {{ $json.customer.email }}
customerPhone = {{ $json.customer.phone }}
orderTotal = {{ $json.total_price }}
paymentStatus = {{ $json.financial_status }}
fulfilmentStatus = {{ $json.fulfillment_status || 'unfulfilled' }}
city = {{ $json.shipping_address.city }}
state = {{ $json.shipping_address.province }}
country = {{ $json.shipping_address.country }}
Line items: Shopify line_items is an array. To get a readable summary in one cell, use:
items = {{ $json.line_items.map(i => `${i.quantity}x ${i.name}`).join(', ') }}
This produces something like: 2x Rose Face Wash, 1x Vitamin C Serum — readable in a spreadsheet cell without extra columns per item.
Date formatting: Shopify sends dates in ISO 8601 (2026-03-09T14:32:00+05:30). If you want a cleaner date in your sheet, use:
orderDate = {{ new Date($json.created_at).toLocaleDateString('en-IN', {timeZone: 'Asia/Kolkata'}) }}
Step 4: Append to Google Sheets
Add a Google Sheets node after Set.
Configuration:
- Operation:
Append or Update Row(use "Append Row" if you never want updates, but Append or Update is safer for idempotency) - Document: Select your Google Sheet (authenticate with Google OAuth first)
- Sheet: Select the tab name
- Columns: Map each n8n field to the corresponding column header
Column matching: n8n will try to auto-match field names to your column headers. If your headers match the variable names you set in the Set node, this is usually automatic. If not, manually map each.
Matching Column (for Append or Update): Set this to orderId. This ensures that if the workflow runs twice for the same order (which can happen with webhooks), it updates the existing row rather than creating a duplicate.
Step 5: Add Error Handling
This is the step most tutorials skip. Don't.
In n8n, if a node fails (Google Sheets API is down, the Shopify payload is malformed, OAuth token expired), the workflow silently stops unless you handle it.
Add an Error Trigger
Create a separate workflow called "Order Sync Error Handler". In that workflow, add an n8n Error Trigger node as the first node. This workflow fires whenever any other workflow fails.
In the Error Handler workflow:
- Error Trigger → receives the error details
- Slack node (or Gmail) → sends you a message with the workflow name, the error message, and the order ID that failed
- Google Sheets node → logs the failed order to an "Errors" tab in your sheet
Then, in your main Shopify → Sheets workflow, go to Workflow Settings → Error Workflow and point it to this error handler.
Now if anything breaks, you get a Slack message within seconds. No silent failures.
Step 6: Handle Rate Limits
Shopify's Admin API has rate limits. For most stores, this isn't an issue — the per-order workflow rarely hits limits unless you're doing a bulk historical import.
If you're processing more than 2 API calls per second in n8n (which might happen if you're backfilling old orders), add a Wait node between API calls to introduce a 500ms delay.
Google Sheets API also has limits: 100 requests per 100 seconds per user. For normal order volumes this is never an issue. If you're doing bulk imports, batch your writes.
The Gotchas
Things I've run into that other guides don't mention:
1. Shopify webhook HMAC verification
If you're using the raw Webhook node instead of the Shopify Trigger node, Shopify signs every webhook with an HMAC-SHA256 hash in the X-Shopify-Hmac-Sha256 header. You should verify this. n8n doesn't do it automatically on the raw Webhook node.
Add a Function node after the webhook that verifies the signature:
const crypto = require('crypto');
const secret = 'YOUR_SHOPIFY_WEBHOOK_SECRET';
const body = $input.first().json.rawBody;
const hmac = $input.first().headers['x-shopify-hmac-sha256'];
const digest = crypto.createHmac('sha256', secret).update(body).digest('base64');
if (digest !== hmac) throw new Error('Invalid webhook signature');
return $input.all();
The Shopify Trigger node handles this for you automatically — another reason I prefer it.
2. fulfillment_status is null, not "unfulfilled"
Shopify sends fulfillment_status: null for unfulfilled orders, not the string "unfulfilled". If you don't handle this, your Google Sheet will have blank cells in that column. Use {{ $json.fulfillment_status || 'unfulfilled' }} as shown in Step 3.
3. Customer object can be null on guest checkouts
If a customer checks out as a guest, $json.customer may be null. Use optional chaining:
customerName = {{ $json.customer?.first_name || '' }} {{ $json.customer?.last_name || '' }}
customerEmail = {{ $json.billing_address?.email || $json.contact_email || '' }}
4. Webhook duplicate delivery
Shopify guarantees "at least once" delivery, not "exactly once". Under high load or network issues, the same webhook can fire twice. The Append or Update Row setup with orderId as the matching column handles this by updating the existing row rather than creating a duplicate. Don't use plain Append Row if you care about duplicates.
5. n8n credential expiry
Google OAuth tokens expire. Set up n8n's credential auto-refresh, or you'll wake up to a workflow that's been silently failing for hours. The Error Handler workflow from Step 5 is your safety net here.
What This Replaced (Real Client Example)
The beauty brand I mentioned: 45 minutes/day of manual CSV export. At their ops manager's time cost, that's roughly Rs 15,000/month in manual effort.
The n8n workflow took about 3 hours to build, test, and deploy on a Rs 600/month VPS (it runs alongside 4 other workflows for the same client). Total monthly cost of the automation infrastructure: Rs 600.
The workflow has been running for 8 months. Zero manual intervention. One error in that time (Google OAuth token expired), caught by the Error Handler, resolved in 5 minutes.
Extending the Workflow
Once this base workflow is running, here's what's easy to add:
- Fulfilment update sync: Add a second workflow triggered by
Order Updatedevents, updating the existing row when fulfilment status changes - Slack daily summary: A scheduled workflow that runs at 8am, reads yesterday's rows from the sheet, and posts a summary to Slack
- COD order flag: Add a branch in Step 3 — if
payment_gateway= "Cash on Delivery", set aneeds_confirmationflag and create a task in ClickUp - Refund logging: A third workflow on the
Refund Createdevent that adds a row to a separate "Refunds" tab
Each extension is its own workflow. Keep them modular — don't chain everything into one massive workflow. Smaller workflows are easier to debug and restart.
Self-Hosted vs n8n Cloud
For this use case, either works.
Self-hosted on a Rs 500–800/month VPS (DigitalOcean, Hetzner, or AWS Lightsail) is what I recommend for clients who want full control and are running 5+ workflows. You own the data, no per-execution charges.
n8n Cloud is better if you want n8n managed for you and don't want to deal with server maintenance. Pricing starts at ~$20/month for 5 active workflows with 2,500 executions/month.
For a single Shopify → Sheets sync doing 300 orders/month, n8n Cloud's starter plan is fine. For a full automation stack, self-host.
FAQ
Can I do this with WooCommerce instead of Shopify? Yes. Replace the Shopify Trigger node with n8n's WooCommerce Trigger node. The field mapping in Step 3 will change (WooCommerce uses different field names), but the Google Sheets logic is identical.
Do I need coding knowledge? For Steps 1–4: no. For the error handling and HMAC verification: basic JavaScript helps but isn't required if you copy the code blocks above.
What if I have historical orders to import? Build a separate one-time workflow using the HTTP Request node to call Shopify's Orders API with pagination, loop through results with n8n's Split In Batches node, and append each batch to your sheet. Don't use the webhook workflow for this.
Is this better than Make.com for this use case? Make.com can do the same thing and is easier to set up. The advantage of n8n here is cost at scale (no per-operation charges), better code execution capability, and self-hosting. If your team is non-technical and you're doing moderate order volumes, Make.com is a fine choice. See our Make.com recipes guide for comparison.
How do I handle multiple Shopify stores? Create one workflow per store. n8n supports multiple Shopify credentials — add each store as a separate credential and duplicate the workflow.
What happens if n8n goes down? Shopify will retry failed webhooks up to 19 times over 48 hours. As long as n8n is back online within that window, you won't lose orders. Use the Error Handler to get alerted when the workflow fails so you can investigate quickly.
If you want this built for your store — or want us to audit your existing n8n setup — book a discovery call. We typically scope and deploy this kind of integration in one working day.