HomeShopify & E-commerceShopify refund tracker in Google Sheets…

Shopify refund tracker in Google Sheets with n8n

Shopify refund tracker in Google Sheets with n8n

New to n8n? Start with our step-by-step setup guide, then come back to build this workflow.










TL;DR: This guide builds a Shopify refund tracker in Google Sheets with n8n. Every time a refund is issued, a Shopify trigger sends it to n8n, a Code node flattens the data, and a Google Sheets node appends one row with the order, customer, amount and reason. An IF node pings Slack when a refund crosses your high-value threshold, so finance sees big hits in real time without watching the admin all day.

Refunds are the part of a Shopify store nobody likes to look at, which is exactly why they pile up unnoticed. The admin shows you each refund on its own order page, but it never gives you a single running list you can sort, total, or hand to your accountant. By the end of the month you are clicking through orders one at a time trying to reconstruct what went out the door. If you have ever wondered why your payout was lighter than expected, an unlogged refund is usually hiding in there somewhere.

A refund tracker fixes that quietly in the background. This is one of the most useful entries in our n8n Shopify automation collection because it costs almost nothing to run and pays for itself the first time it catches a refund you would have missed. You keep one Google Sheet that fills itself, and you get a Slack ping the moment a large refund goes through.

Prefer to skip the setup? Grab the ready-made template and have your refund log filling itself in under ten minutes.

What it does

The workflow listens for one Shopify event: a refund being created. The moment a refund is issued from the Shopify admin, from a customer self-service return, or through an app, Shopify sends the full refund record to n8n. n8n reads the parts that matter, writes a row to your Google Sheet, and decides whether the refund is big enough to need a human looking at it right away.

Here is what you end up with running quietly behind your store:

  1. A refund is issued in Shopify for any reason, full or partial.
  2. Within a second or two, a new row appears in your Google Sheet with the order number, customer, amount, reason and date.
  3. If the refund is at or above your threshold, say one hundred dollars, a message lands in your Slack channel naming the order and the amount.
  4. At month end you sort the sheet, total the refund column, and reconcile against your payouts in minutes instead of an afternoon.

Why it beats the default Shopify export

Shopify does let you export orders to CSV, and the export does include refund columns. So why build anything? Three reasons.

The export is a manual pull. You have to remember to run it, choose a date range, download the file, and open it. A tracker that writes itself never gets forgotten on a busy week. The export is also order-shaped, not refund-shaped. One order can carry several refunds over time, and the flat CSV makes those awkward to read. A purpose-built refund row gives you one line per refund event with only the fields you care about.

The export tells you nothing in the moment. A two hundred dollar refund and a five dollar refund look the same in a file you open next week. With a live tracker, the big ones interrupt you on purpose while the small ones log themselves silently. That separation is the whole point. You stop watching refunds you do not need to watch and you never miss the ones you do.

What you need

  • A running n8n instance, either n8n Cloud or self-hosted. Any recent version works.
  • A Shopify store with admin access so you can create an app token with read access to orders.
  • A Google account with one Sheet ready to receive the refund rows.
  • A Slack workspace and a channel for the alerts. You can use Telegram instead if you prefer, the swap is a single node.

Build time is around thirty to forty minutes from scratch the first time, mostly spent connecting credentials. With the ready template it drops to under ten minutes because the nodes and field mappings are already wired.

The nodes at a glance

Five nodes carry the whole job. Read them top to bottom and you have the full mental model before touching n8n.

Order Node Type Job
1 Refund Created Shopify Trigger Receives every refund the moment it happens
2 Flatten Refund Code Pulls clean fields out of the nested payload
3 Append To Sheet Google Sheets Writes one row per refund to the log
4 Is High Value IF Checks the amount against your threshold
5 Alert Slack Slack Posts large refunds for a human to review
┌───────────────────────────────────────────────────────────────┐
│  SHOPIFY REFUND TRACKER                                         │
│                                                                │
│  [Refund Created] → [Flatten Refund] → [Append To Sheet]       │
│                                              ↓                  │
│                                        [Is High Value?]        │
│                                          ↓ yes    ↓ no         │
│                                     [Alert Slack]  (stop)      │
└───────────────────────────────────────────────────────────────┘
  

Step-by-step build

1 Refund Created (Shopify Trigger)

This node is the front door. It subscribes to a single Shopify webhook topic and wakes the workflow only when that event fires.

  1. Add a Shopify Trigger node and open it.
  2. Connect your Shopify credential. If you have not made one yet, create a custom app in the Shopify admin under Settings, then Apps and sales channels, then Develop apps, and give it read access to orders.
  3. Set the topic to Refund Created. Save the node so n8n registers the webhook with Shopify.

When a refund fires, the node outputs the raw Shopify refund object. It is deeply nested, which is why the next node exists.

💡

Tip: While building, use the Listen for test event button and issue a one dollar test refund on a draft order. You get real payload shape to map against instead of guessing field names.

2 Flatten Refund (Code)

The raw refund payload buries the useful numbers inside arrays. A short Code node reads them once and hands forward a clean, flat object that every later node can reference without digging.

const r = $json;

// Refund line items hold the actual money returned
const lines = r.refund_line_items || [];
const refundedItems = lines.reduce((sum, li) => sum + Number(li.subtotal || 0), 0);

// Transactions hold the cash actually moved back to the customer
const tx = r.transactions || [];
const refundedCash = tx.reduce((sum, t) => sum + Number(t.amount || 0), 0);

const amount = refundedCash || refundedItems;

return [{
  json: {
    refund_id: r.id,
    order_id: r.order_id,
    order_number: r.order_number || '',
    customer: r.note_customer_name || 'Unknown',
    amount: Number(amount.toFixed(2)),
    currency: (tx[0] && tx[0].currency) || 'USD',
    reason: r.note || 'No reason given',
    created_at: r.created_at
  }
}];

After this node, the data is simple and predictable:

{
  "refund_id": 99887766,
  "order_number": "1042",
  "customer": "Emily Rodriguez",
  "amount": 129.00,
  "currency": "USD",
  "reason": "Item arrived damaged",
  "created_at": "2026-06-06 14:30"
}
📌

Field names in the real Shopify payload can vary slightly by app and store setup. Run one test refund and confirm the keys match before you trust the mapping in production.

3 Append To Sheet (Google Sheets)

Now the clean record gets written to your log. One refund in, one row out.

  1. Add a Google Sheets node and connect your Google credential.
  2. Set the operation to Append.
  3. Pick your spreadsheet and the tab, for example a tab named Refunds.
  4. Map each column to a field from the Code node, for instance the Amount column to {{ $json.amount }} and the Customer column to {{ $json.customer }}.

That is the core tracker done. Refunds now log themselves. The last two nodes add the part that saves you from surprises.

4 Is High Value (IF)

Most refunds are small and routine, and you do not want a ping for every five dollar return. The IF node splits the flow so only the refunds worth your attention go further.

  1. Add an IF node after the Sheets node.
  2. Set the condition to Number, value one {{ $json.amount }}, operation is greater than or equal, value two 100.
  3. The true output carries large refunds onward. The false output ends quietly, the row is already saved.
💡

Tip: Set the threshold to whatever a meaningful refund is for your store. A jewelry shop might use three hundred dollars, a low-ticket store might use forty. You can change it any time without touching another node.

5 Alert Slack (Slack)

The final node turns a big refund into a message a person actually sees.

  1. Add a Slack node on the true branch and connect your Slack credential.
  2. Set the operation to send a message and choose your channel, for example #refunds.
  3. Write a message that pulls the key fields, such as: Refund alert: {{ $json.currency }} {{ $json.amount }} on order {{ $json.order_number }} for {{ $json.customer }}. Reason: {{ $json.reason }}

To use Telegram instead, drop in a Telegram node here, set your chat ID, and map the same text. Nothing else in the workflow changes.

The Google Sheet structure

Keep the sheet simple. These columns match the Code node output one to one, which makes the mapping in step three painless.

Column Type Example Description
Date Date 2026-06-06 14:30 When the refund was created
Order Text 1042 The Shopify order number
Customer Text Emily Rodriguez Who the refund went to
Amount Number 129.00 The refunded value
Currency Text USD Currency of the refund
Reason Text Item arrived damaged Why the refund was issued

Two sample rows show how the log reads day to day:

Date Order Customer Amount Currency Reason
2026-06-06 14:30 1042 Emily Rodriguez 129.00 USD Item arrived damaged
2026-06-06 16:12 1045 Michael Chen 18.50 USD Wrong size
📌

Put the column headers in row one exactly as named, then point the Sheets node at that header row. n8n matches on header text, so a typo in the sheet is the most common reason a column comes up blank.

Common mistakes

A handful of small things trip people up the first time. Knowing them in advance saves a frustrating half hour.

The first is mapping the wrong amount field. The Shopify refund payload contains several money figures, and grabbing the order total instead of the refunded total gives you numbers that look right but are not. The Code node above reads the transaction amount on purpose, so trust it and verify with one real refund.

The second is forgetting to set the app token scope. If your Shopify custom app only has read access to products, the refund webhook will not deliver order data. Give it read access to orders and reinstall the app if you changed scopes after the first install.

The third is a threshold set in the wrong place. Some people put the high-value check before the Sheets node, which means small refunds never get logged. Always append to the sheet first, then branch. Every refund belongs in the log, the IF node only decides who gets a ping.

The last is leaving the workflow inactive. Building and testing happens in the editor, but a saved workflow does nothing until you flip the Active switch in the top corner. Test, save, then activate, in that order.

What it costs at realistic volume

This is one of the cheapest automations you can run, which is part of why it is worth doing. There are no paid AI calls and no per-message fees in the core build.

Say your store does eight hundred orders a month with a healthy five percent refund rate. That is roughly forty refunds a month, or forty workflow executions. On n8n Cloud the entry plan covers thousands of executions, so forty barely registers. On a self-hosted n8n on a small VPS, the cost is effectively zero beyond the server you already run. Google Sheets and Slack both handle this volume on their free tiers without trouble.

Even a high-volume store doing ten thousand orders a month with the same refund rate lands around five hundred executions, still comfortably inside normal n8n limits. The economics do not change as you grow, which is the opposite of paying per refund inside a third-party app.

Get the Shopify refund tracker template

You now know exactly how the tracker works. If you would rather not wire five nodes by hand, the ready-made template imports in minutes with every field already mapped, plus a setup guide and a credentials guide so the connections take seconds.

Get the template
Have us set it up for you

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

Does this work on the Shopify Basic plan?

Yes. The Refund Created webhook is available on every Shopify plan, including Basic. You only need a custom app token with read access to orders, which any plan can create from the admin under Settings and Develop apps.

Will it capture partial refunds?

Yes. Shopify fires the Refund Created event for both full and partial refunds. The Code node reads the actual refunded amount from the transactions, so a partial refund logs the partial value rather than the full order total.

Can I send the alert to Telegram instead of Slack?

Yes. Swap the Slack node for a Telegram node on the true branch of the IF. Map the same message text to the Telegram Text field and set your chat ID. Everything else in the workflow stays exactly the same.

What happens if Google Sheets is down when a refund fires?

n8n marks that execution as failed and keeps it in the execution log. Turn on retries on the Sheets node and add an error workflow so nothing slips through. You can also re-run any failed execution by hand from the editor.

Does it handle multiple currencies?

The payload includes the currency code, so log it in its own column. If you sell in several currencies, convert to a base currency inside the Code node before the high-value check so your alert threshold stays consistent across stores.

Related guides

n8n
Shopify
Google Sheets
Slack
automation