Shopify refund alert to Telegram with n8n









A Shopify refund alert to Telegram built in n8n pings your phone the second any refund is issued, with the order ID, the amount returned and the reason attached. Most store owners only find out about refunds when they scan the admin or reconcile at month end, by which point a spike in returns has already been quietly draining margin for days. This guide builds the alert from scratch with three nodes, then hands you a ready-to-import template if you would rather skip the wiring.

What it does

The workflow listens for Shopify’s refunds/create event. Every time you or a staff member issues a refund, whether it is the full order or a single returned item, Shopify sends the refund payload to n8n. The workflow reads the transaction amounts, sums them into one clean total, pulls the order ID and the refund note, and sends a formatted message straight to your Telegram chat.

You end up with a running feed of refunds in the same place you already check messages. No dashboard to open, no report to wait for. A typical alert reads like this:

💸 Refund issued
Order ID: 5081234567890
Amount: 49.00 USD
Reason: Wrong size sent
Time: 2026-09-02T14:30:00-04:00

Seeing refunds as they happen changes how you react to them. A single refund is noise, but three in an afternoon for the same product is a signal, and you catch that pattern hours earlier when the alerts are stacking up in front of you instead of buried in a report you read on Friday. Faster awareness means you can pause a listing, flag a supplier, or check a shipment before the same problem refunds ten more orders.

Why it beats the default

Shopify does send a staff email on some refund actions, but those land in a shared inbox, they are easy to filter away, and they say nothing about how the day’s refunds are trending. If three refunds hit in an hour because a batch shipped with the wrong variant, an email digest will not make that obvious. A live Telegram feed will, because you watch the messages stack up in real time.

Telegram also travels better than email. The alert reaches your phone as a push notification, it works for a whole team if you point it at a group, and it stays readable on a two inch lock screen. Because the message is built from the raw webhook, you control exactly what it says, so you can strip it down to the three facts that matter or add order tags, customer email, or a link back to the order in Shopify admin.

What you need

  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify store with a custom app and Admin API access token. If you have not connected Shopify to n8n yet, follow how to connect Shopify to n8n in 2026 first. It uses the current Dev Dashboard method, not the removed legacy custom-app flow.
  • A Telegram account, a bot created through @BotFather, and the chat ID you want alerts sent to.
  • About 20 minutes to build it by hand, or under 5 minutes with the template.

Node-by-node list

Three nodes, one straight line. No branches, no code node.

[Refund created (Shopify)]  -->  [Format refund message]  -->  [Send Telegram alert]
   shopifyTrigger                 set (Edit Fields)              telegram (sendMessage)
   topic: refunds/create          builds the message text        posts to your chat
  
Node Type Job
Refund created (Shopify) shopifyTrigger Fires on the refunds/create webhook
Format refund message set (v3.4) Sums transaction amounts and composes the alert text
Send Telegram alert telegram (v1.2) Sends the message to your Telegram chat

Step-by-step build

  1. Add the Shopify trigger. Drop a Shopify Trigger node onto the canvas. Set Authentication to Access Token and pick your Shopify credential (created during the connect guide above). Set the Topic to Refund created (refunds/create). When you save, n8n registers the webhook with Shopify automatically.
  2. Add the Edit Fields node. Connect an Edit Fields (Set) node after the trigger. This is where you turn the raw refund payload into a clean sentence. The refund webhook carries a transactions array, and each entry has its own amount and currency, so a single refund can span more than one transaction. You sum them so the alert shows one figure.
  3. Build the message field. In the Set node add a String field named message and paste this expression:
    =💸 <b>Refund issued</b>
    Order ID: {{ $json.order_id }}
    Amount: {{ ($json.transactions || []).reduce((s, t) => s + parseFloat(t.amount || 0), 0).toFixed(2) }} {{ ($json.transactions && $json.transactions[0]) ? $json.transactions[0].currency : ($json.currency || '') }}
    Reason: {{ $json.note || 'No reason provided' }}
    Time: {{ $json.created_at }}

    The reduce adds every transaction amount, toFixed(2) keeps it to two decimals, and the fallbacks stop the message breaking when a field is empty.

  4. Add the Telegram node. Connect a Telegram node, set Resource to Message and Operation to Send Message. Select your Telegram credential. In Chat ID paste the chat you want alerts in. In Text use ={{ $json.message }}.
  5. Turn on HTML formatting. Open Add Field in the Telegram node, add Parse Mode and set it to HTML. That renders the <b> tag as bold. Save the workflow and toggle it Active.
💡

Tip: To make each alert clickable, add one line to the message: Order: https://admin.shopify.com/store/YOUR-STORE/orders/{{ $json.order_id }}. Tapping it opens the order in Shopify admin from your phone.

Common mistakes

  • Reporting only the first transaction. If you reference transactions[0].amount directly, a refund split across two transactions under-reports the total. Summing the array with reduce is what keeps the figure honest.
  • Forgetting Parse Mode. Leave it off and the alert shows a literal <b> in the text. Set Parse Mode to HTML so the tag renders.
  • Using the wrong chat ID. A personal chat ID is a positive number; a group ID is negative and includes the minus sign. Sending to a group without the minus sign returns a “chat not found” error.
  • Bot never messaged first. Telegram bots cannot start a conversation. Send your bot any message once (or add it to the group) before the first run, or the send silently fails.
  • Expecting alerts on unpaid cancellations. Cancelling an order that was never paid issues no refund, so no webhook fires. That is Shopify behavior, not a bug in the workflow.

Cost at realistic volume

This is one of the cheapest workflows you can run. The Telegram Bot API is free, and the caps it does have (dozens of messages per second) are far beyond any refund volume a normal store produces. Shopify’s webhook is included in every plan.

Volume Telegram Shopify webhook n8n executions
50 refunds / month $0 $0 50
500 refunds / month $0 $0 500
2,000 refunds / month $0 $0 2,000

On self-hosted n8n each of those executions costs nothing. On n8n Cloud they count toward your plan’s execution quota, and even at 2,000 refunds a month you are well inside the entry tier. There is no per-message fee anywhere in the chain.

Compare that to a paid refund-notification app from the Shopify App Store, where a recurring monthly fee buys you roughly the same alert. Once the workflow is built you own it outright, you can change the message wording whenever you like, and you can point it at any channel your team already uses. The only ongoing cost is the n8n instance you are almost certainly running other automations on already.

Ready-to-import template

🚀 Shopify refund alert to Telegram (n8n template)

The exact three-node workflow from this guide, exported and ready to import. Drop in your Shopify and Telegram credentials, set your chat ID, and you are live in a few minutes. The guide above is free to follow; the download just skips the build.

Download the template ($9) →

Instant download · Works on n8n Cloud and self-hosted · Prefer it done for you? See our done-for-you setup service.

Frequently asked questions

Does this alert fire for partial refunds too?

Yes. Shopify fires the refunds/create webhook for every refund, whether you return the full order total or a single line item. The workflow sums the transaction amounts, so a partial refund shows its own smaller figure instead of the full order value.

Will I get a message when an order is cancelled?

Only if the cancellation actually issues a refund. Cancelling an unpaid order creates no refund transaction, so no alert fires. When you cancel a paid order and refund it, Shopify records a refund and the Telegram message arrives as normal.

Can I send the alert to a group or channel instead of my personal chat?

Yes. Add your bot to the Telegram group or channel, then use that chat ID in the Send Telegram alert node. Group IDs start with a minus sign. Everyone in that chat then sees each refund the moment it is issued.

How much does it cost to run?

Nothing beyond n8n itself. The Telegram Bot API is free with no message caps that a normal store would reach, and the Shopify webhook is included in your plan. On self-hosted n8n the whole workflow runs at zero marginal cost per refund.

What if my n8n instance is down when a refund happens?

Shopify retries a failed webhook delivery for up to 48 hours, so a short outage rarely loses an alert. If n8n is offline for longer, that refund notification is missed. For a permanent record, pair this with a refund tracker that logs every refund to Google Sheets.

Related guides

Shopify product auto-tagging with n8n









Shopify product auto-tagging with n8n keeps your catalog organized without anyone remembering to do it. The moment a new product is created, a webhook fires, a rule step builds tags from the product’s vendor, type, and price, and the product updates itself. Consistent tags are what power your automatic collections, storefront filters, and every workflow that routes products by category, so getting them right on every product, every time, quietly makes the rest of your store work better.

What it does

Tags are the connective tissue of a Shopify store. Automatic collections build from them, storefront filters read them, and downstream automations branch on them. But tags are only useful if they are applied consistently, and manual tagging never is. Someone forgets the vendor tag, someone types “mid range” one day and “mid-range” the next, and your filters quietly break.

This workflow removes the human from that loop. Whenever a product is created, it reads the product and assigns a clean, consistent set of tags: the vendor, the product type, and a price band worked out from the lowest variant price. It merges those with any tags already on the product, removes duplicates, and writes them back. A new product goes from untagged to fully classified in a second, for example:

Product:  Merino Crew Sweater
Vendor:   Acme
Type:     Sweater
Price:    from 49.00
Tags:     Acme, Sweater, mid-range

Every product gets the same treatment, so your collections and filters can trust the tags underneath them.

Why it beats the default

The manual default is tagging each product by hand at upload time, which is exactly when you are busy writing the description and setting the price, so tags get skipped or done inconsistently. Shopify has no built-in rule engine that tags products by their own attributes, so there is nothing catching the misses.

An automated rule is consistent by definition. The same logic runs on every product, so “mid-range” is always spelled the same way and the vendor tag is never forgotten. That consistency is what lets an automatic collection like “All premium items” or a storefront filter by vendor actually work. Compared with Shopify Flow, which is limited to Shopify Plus, n8n runs this on any plan and lets you tag on any rule you can express in a line of code.

It also composes with the rest of your catalog work. Bulk uploads, supplier feeds, and manual additions all pass through the same tagging, so however a product enters your store, it comes out classified the same way.

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token with read and write access to products, created through the 2026 Shopify Dev Dashboard method. New to this? Follow connect Shopify to n8n (2026 guide) first.
  • A tagging scheme in mind: which attributes matter and where your price bands sit.

Build time is about 15 minutes from scratch, or a couple of minutes if you import the ready-made template below and adjust the rules.

Node-by-node list

Three nodes, in a straight line. Here is each one and what it does.

# Node Type Job
1 New Shopify product shopifyTrigger Fires on the products/create webhook for every new product, delivering the full product payload.
2 Build tags code Reads the vendor, type, and lowest variant price, works out the tags and price band, and merges them with existing tags.
3 Update product tags shopify Writes the merged tag list back to the product.

How it works

  [New Shopify product]  (products/create webhook)
          |
          v
  [Build tags]  (vendor + type + price band, merged with existing)
          |
          v
  [Update product tags]  (write tags back to the product)
  

Step-by-step build

  1. Create a new workflow in n8n and name it “Shopify product auto-tagging.”
  2. Add a Shopify Trigger node. Set Authentication to Access Token and attach your credential. Set the Topic to products/create. When you activate the workflow, n8n registers this webhook in your store for you.
  3. Add a Code node named “Build tags” after it, in Run Once for Each Item mode. It reads the product, adds the vendor and product type as tags, and assigns a price band from the lowest variant price. The bands are two numbers you can change:
    if (min < 25) band = 'budget';
    else if (min <= 100) band = 'mid-range';
    else band = 'premium';

    It merges the new tags with the product’s existing tags and removes duplicates, returning the product id and the final tag string.

  4. Add a Shopify node named “Update product tags” after it. Resource Product, Operation Update. Set Product ID to {{ $json.productId }}. Under Update Fields add Tags with the value {{ $json.newTags }}, which writes the merged list back without losing anything.
  5. Save, then create a test product in Shopify. Confirm it picks up the vendor, type, and price-band tags, then toggle the workflow Active so every new product is tagged automatically.
💡

Tip: to classify your existing catalog too, duplicate this workflow and replace the trigger with a Schedule Trigger plus a Shopify Get Many products step. The same Build tags logic then runs across every product. Run it once to backfill, then rely on the webhook version for anything new.

Common mistakes

  • Overwriting existing tags. A product update replaces the whole tag list. The Build tags step merges with the current tags first, so keep that merge or you erase manual tags.
  • Reading a single variant’s price. A product can have several variants at different prices. The step takes the lowest variant price for the band, so a product with a cheap size is not miscategorized as premium.
  • Inconsistent band spelling. If you hand-edit the band names, spell them identically everywhere, since a collection filtering on mid-range will not match mid range.
  • Read-only token. Tagging is a write. If your Shopify token cannot write products, the read works but the update fails. Grant write access to products on the credential.
  • Expecting old products to update. The products/create webhook only fires for new products. Use the schedule variant above to tag the catalog you already have.

Cost at realistic volume

This workflow runs inside free tiers at any catalog size. The Shopify Admin API is included with your plan, and one tag write per new product is well within rate limits. There is no third-party service to pay for, since the tags live on Shopify.

On n8n, each new product runs three quick steps. Even a store adding hundreds of products a month runs this for nothing on n8n Cloud’s entry plans and free on self-hosted n8n. The payoff is a catalog that tags itself correctly forever, which is the foundation your collections, filters, and other automations quietly depend on.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 15 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your credentials and rules. Prefer it done for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($12) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

What tags does it assign?

Out of the box, three: the product’s vendor, its product type, and a price band of budget, mid-range, or premium based on the lowest variant price. The rules live in one small Code step, so you can add, remove, or rename any of them. The point is consistent tags applied the same way to every product.

Will it wipe my existing product tags?

No. The workflow reads the product’s current tags, adds the new ones, and removes duplicates before writing back. Anything you tagged by hand stays. A product that already has a seasonal tag keeps it and simply gains the vendor, type, and price-band tags on top.

Can I tag existing products, not just new ones?

Yes. Swap the products/create trigger for a Schedule Trigger plus a Shopify Get Many products step, and the same tagging logic runs across your whole catalog. Run it once to backfill every product, then keep the webhook version active so new products are tagged the moment you add them.

How do I change the price bands?

The thresholds are two numbers in the Code step: under 25 is budget, 25 to 100 is mid-range, and above is premium. Change those numbers to your own price points, or add more bands. Because it reads the lowest variant price, a product with a cheap size still lands in the right band.

Can I tag by other rules, like a title keyword?

Yes. The Code step has the full product object, so you can add rules on the title, SKU, weight, tags, or any field. A line that adds a sale tag when the title contains clearance, or an oversized tag above a weight, is a couple of lines. The vendor, type, and band rules are just a starting set.

Related guides

Shopify return rate by product report with n8n









A Shopify return rate by product report with n8n tells you the one thing a blended refund number hides: which products keep coming back. Once a month this workflow reads your recent orders and their refunds, groups units sold and units returned by product, and writes a ranked table to Google Sheets. The item at the top of that list is where a photo, a size chart, or a supplier is quietly costing you money. Built in four n8n nodes, no manual spreadsheet math.

What it does

Every store has an overall return rate, and it is nearly useless on its own. A store at 8 percent might have forty products returning at 2 percent and three returning at 40 percent. The average tells you nothing about which three to fix. You need the rate per product, and Shopify does not report it.

This workflow builds it. On a schedule it pulls the orders from your report window, reads each order’s line items for units sold and its refund line items for units returned, and groups both by product. Then it computes a return rate for every product and writes the ranked result to a Google Sheet, worst offenders first. A report looks like this:

Product                     Ordered  Refunded  Return rate
Merino Crew Sweater             120       31       25.8%
Slim Chino Pant                  95       14       14.7%
Canvas Tote Bag                 210        9        4.3%
Ceramic Mug 12oz                340        4        1.2%

The Merino Crew line is doing real damage, and now you can see it, name it, and go fix the cause.

Why it beats the default

The manual default is exporting orders and refunds to a spreadsheet and building the per-product math by hand, which is fiddly enough that almost no one does it monthly. Shopify’s analytics show returns in aggregate, not a clean per-product rate you can sort and act on. So the products that quietly drive most of your refunds stay invisible.

An automated report surfaces them and keeps a history. Because each run appends dated rows, you can watch a product’s return rate fall after you swap a supplier or fix a size chart, which is the proof that your fix worked. Compared with Shopify Flow, which is limited to Shopify Plus and does no aggregation, n8n does the grouping in a Code step and sends the table anywhere you like.

It also completes the returns picture. A refund tracker logs each refund as it happens; this report turns those refunds into a rate per product, so you move from “we refunded $2,400 last month” to “this one product is why.”

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token, created through the 2026 Shopify Dev Dashboard method. New to connecting the two? Follow connect Shopify to n8n (2026 guide) first.
  • A Google account with a Sheet to hold the report.

Build time is about 25 minutes from scratch, or a couple of minutes if you import the ready-made template below and add your credentials.

Node-by-node list

Four nodes in a straight line. Here is each one and what it does.

# Node Type Job
1 First of month scheduleTrigger Runs the report once a month at a set hour.
2 Get orders shopify Pulls recent orders of any status, each carrying line_items and a refunds array.
3 Return rate per product code Groups units ordered and units refunded by product, then computes and ranks the return rate.
4 Append to Sheet googleSheets Writes one row per product to your report sheet, worst return rate first.

The report sheet

Create a Google Sheet first, with a header row whose columns match the fields the report produces exactly, because the append step maps input fields to columns by name.

Column Meaning Example
Period_end Date the report was run 2026-09-01
Product Product title Merino Crew Sweater
Product_id Shopify product id 8123456789
Units_ordered Units sold in the window 120
Units_refunded Units refunded in the window 31
Return_rate_pct Refunded divided by ordered 25.8
📌

Note: the header names must match the field names above exactly, since the append step uses automatic mapping. One product gets one row per run, so filtering the sheet by Product gives you that product’s return-rate trend over time.

How it works

  [First of month]  (monthly schedule)
          |
          v
  [Get orders]  (line_items + refunds[])
          |
          v
  [Return rate per product]  (ordered vs refunded units, grouped by product)
          |
          v
  [Append to Sheet]  (one ranked row per product)
  

Step-by-step build

  1. Create a new workflow in n8n and name it “Shopify return rate by product.”
  2. Add a Schedule Trigger node named “First of month.” Set it to run monthly, on day 1, at an early hour such as 7 AM.
  3. Add a Shopify node named “Get orders.” Set Authentication to Access Token and attach your credential. Resource Order, Operation Get Many, Return All on. Under Filters set Status to any (so refunded and closed orders are included) and Created At Min to {{ $now.minus({ days: 35 }).toISO() }}.
  4. Add a Code node named “Return rate per product” after it, in Run Once for All Items mode. It walks each order’s line_items to tally units sold per product and its refunds[].refund_line_items to tally units returned, groups both by product id, and returns one row per product with the return rate, sorted worst first. The window is one line:
    const cutoff = $now.minus({ days: 35 }).toMillis();

    Widen it to 60 or 90 days if your returns arrive later.

  5. Create a Google Sheet with the six header columns from the table above. Add a Google Sheets node named “Append to Sheet,” operation Append, and pick your document and sheet. Leave mapping on Auto-map Input Data so each product row lands in the matching columns.
  6. Save, run the workflow once by hand, and confirm your products appear in the Sheet with ordered, refunded, and rate filled in. Then toggle it Active.
💡

Tip: turn the report into an alert. Add an IF after the Code node that keeps only products with Return_rate_pct over, say, 20, and send those to Telegram. You get the full monthly table in Sheets and an immediate ping whenever a product crosses your pain threshold.

Common mistakes

  • Leaving the status filter on the default. Shopify’s order list defaults to open orders. Set Status to any or you miss the closed and refunded orders that carry the returns you are counting.
  • Dividing by the wrong base. Return rate is refunded units over ordered units for that product, not over total store orders. The Code node keeps each product’s own counts, so the rate is per product.
  • Header names that do not match. Auto-mapping writes by column name. If a header is off by a character, that column stays blank. Copy the field names exactly.
  • Too short a window for slow returns. If most returns land 40 days out, a 35-day window undercounts them. Match the window in both the filter and the Code node to how long your returns actually take.
  • Reading refunds as money. This report counts refunded units, not refunded dollars. A partial-dollar refund with no unit returned will not inflate the rate, which is what you want for a returns signal.

Cost at realistic volume

This workflow runs inside free tiers at normal store volume. The Shopify Admin API is included with your plan, and Google Sheets is free. One monthly run reads a month of orders and writes a handful of product rows, well within any rate limit.

On n8n, the cost is one execution a month that fans over the window’s orders in a single Code step. Even a store doing thousands of orders a month runs this comfortably on n8n Cloud’s entry plans and free on self-hosted n8n. In exchange you get the one report that points straight at the products quietly driving your refunds.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 25 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your credentials and sheet. Prefer it done for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($16) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

How is the return rate calculated?

For each product, it is refunded units divided by ordered units, across the orders placed in the report window. The workflow reads every order’s line items for units sold and its refund line items for units returned, groups both by product, and reports the percentage. That gives a per-product rate rather than one blended store number.

Does it count returns or refunds?

It counts refunded line items, because a refund is the concrete signal Shopify records when a return is accepted. If you issue a refund without the item coming back, or refund partially, the units refunded reflect exactly what you refunded. For most stores that tracks real returns closely enough to spot problem products.

Why does a product show up with zero returns?

Because it sold in the window but nothing was refunded, which is a healthy result worth seeing. The report lists every product that had orders, sorted by return rate, so your clean performers sit at the bottom and your problem products rise to the top where they belong.

Returns lag behind orders. How do I handle that?

The default window is the last 35 days, which suits fast-moving stores. If your returns arrive weeks later, widen the window in the Code node and the Shopify filter to 60 or 90 days. You are measuring the return rate of a cohort of orders, so a longer window captures more of their eventual returns.

Can I change the period or send it elsewhere?

Yes. The schedule sets how often it runs and the window is one line in the Code node. The report is a set of rows, so besides Google Sheets you can add a Telegram or Slack step that flags any product over a return-rate threshold, turning the monthly report into an alert when a product starts coming back too often.

Related guides

Shopify fulfillment SLA time-to-ship report with n8n









A Shopify fulfillment SLA time-to-ship report with n8n answers a question most stores only guess at: how long does a paid order actually wait before it ships? Once a week this workflow reads the orders you shipped, measures the hours between order placed and order fulfilled, and reports the average, the median, and the share that met your SLA. The numbers land in a Google Sheet for the trend and in a Telegram message for a quick read, built in five n8n nodes.

What it does

Fast shipping is a promise, and most stores have no idea whether they keep it. Shopify shows individual orders, but it does not tell you that your average order took 31 hours to ship last week, or that 12 percent blew past your two-day target. Without that number you cannot tell whether fulfillment is improving, slipping, or fine.

This workflow turns raw orders into that number. On a schedule it pulls the orders shipped in the last week, calculates each one’s time from placed to fulfilled, and rolls the set up into a short report: how many orders shipped, the average and median hours to ship, and the percent that met your SLA. It writes one row to a Google Sheet so you build a week-over-week trend, and it sends a summary to Telegram. The message reads like this:

📦 Fulfillment SLA report (week ending 2026-09-01)
Orders shipped: 214
Avg time to ship: 28.6h
Median: 21.0h
Within 48h SLA: 91.6%

Now the promise has a scoreboard, and you see a slip the week it happens rather than when a customer complains.

Why it beats the default

The manual default is exporting orders to a spreadsheet and building the date math by hand, which almost no one keeps up weekly. Shopify’s analytics cover sales and traffic, but time-to-ship is not a report you can pull, so most teams simply do not track it. What you do not measure, you cannot improve.

An automated report runs itself and keeps history. Because each week appends a row, you get a trend line: the average creeping up before the holidays, the median dropping after you hired a packer. Compared with Shopify Flow, which is limited to Shopify Plus and does no aggregation, n8n does the math in a Code step and sends the result anywhere. You own the SLA number, the period, and the destination.

It also pairs naturally with a real-time delay alert. The alert catches the single order running late right now; this report tells you whether late orders are a pattern. Together they cover both the urgent and the strategic side of fulfillment speed.

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token, created through the 2026 Shopify Dev Dashboard method. New to connecting the two? Follow connect Shopify to n8n (2026 guide) first.
  • A Google account with a Sheet to hold the weekly report, and a Telegram bot token plus the chat id for the summary.

Build time is about 25 minutes from scratch, or a couple of minutes if you import the ready-made template below and add your credentials.

Node-by-node list

Five nodes in a straight line. Here is each one and what it does.

# Node Type Job
1 Every Monday scheduleTrigger Runs the report once a week at a set hour.
2 Get fulfilled orders shopify Pulls recent orders, each carrying a created_at and a fulfillments array with ship times.
3 Compute SLA report code Filters to orders shipped in the window, computes hours-to-ship per order, and rolls up count, average, median, and percent within SLA.
4 Append to Sheet googleSheets Adds one row to your report sheet so weeks stack into a trend.
5 Send Telegram summary telegram Posts the week’s numbers to your team channel.

The report sheet

Create a Google Sheet first, with a header row whose columns match the fields the report produces exactly, because the append step maps input fields to columns by name.

Column Meaning Example
Period_end Date the report was run 2026-09-01
Orders_shipped Orders fulfilled in the window 214
Avg_hours_to_ship Average hours from placed to shipped 28.6
Median_hours_to_ship Typical order’s hours to ship 21.0
SLA_hours Your target, in hours 48
Pct_within_SLA Percent that met the target 91.6
📌

Note: the header names must match the field names above exactly, since the append step uses automatic mapping. Get them right once and every future run drops cleanly into place.

How it works

  [Every Monday]  (weekly schedule)
          |
          v
  [Get fulfilled orders]  (created_at + fulfillments[])
          |
          v
  [Compute SLA report]  (hours per order -> avg, median, % within SLA)
          |
          v
  [Append to Sheet]  (one row per week)
          |
          v
  [Send Telegram summary]  (the week's numbers)
  

Step-by-step build

  1. Create a new workflow in n8n and name it “Shopify fulfillment SLA report.”
  2. Add a Schedule Trigger node named “Every Monday.” Set it to run weekly, on Monday, at an early hour such as 7 AM, so the report covers the week just ended.
  3. Add a Shopify node named “Get fulfilled orders.” Set Authentication to Access Token and attach your credential. Resource Order, Operation Get Many, Return All on. Under Filters set Fulfillment Status to shipped and Created At Min to {{ $now.minus({ days: 10 }).toISO() }} so you pull a little more than a week and let the Code node trim to the exact window.
  4. Add a Code node named “Compute SLA report” after it, in Run Once for All Items mode. It reads every order, keeps those whose first fulfillment shipped within the last seven days, and computes the hours from created_at to the fulfillment time. It returns a single row with the count, average, median, SLA target, and percent within SLA. The SLA target lives in one line:
    const slaHours = 48;   // your target, in hours

    Change 48 to your own target and the whole report follows.

  5. Create a Google Sheet with the six header columns from the table above. Add a Google Sheets node named “Append to Sheet,” operation Append, and pick your document and sheet. Leave mapping on Auto-map Input Data so each computed field lands in its matching column.
  6. Add a Telegram node named “Send Telegram summary.” Attach your bot credential, set the chat id, and write a short message that reads the report values, for example the average with {{ $('Compute SLA report').item.json.Avg_hours_to_ship }}. Referencing the Code node by name keeps the numbers correct even though the Sheets step ran in between.
  7. Save, run the workflow once by hand to confirm a row appears in the Sheet and the Telegram message arrives, then toggle it Active.
💡

Tip: want a per-order breakdown, not just the weekly totals? Have the Code node return one item per order with its hours-to-ship, and point the Sheets append at a second tab. You then keep both a weekly trend and a line-by-line log you can sort to find the slowest orders.

Common mistakes

  • Counting unshipped orders. Time-to-ship only makes sense for orders that shipped. The Code node skips any order without a fulfillment, so a pile of open orders does not distort the average.
  • Header names that do not match. Auto-mapping writes by column name. If the sheet says Avg hours but the field is Avg_hours_to_ship, that column stays blank. Copy the names exactly.
  • Double counting across weeks. The cutoff keeps each run to orders shipped in the last seven days, so a run does not re-report last week’s orders. Keep the schedule and the cutoff on the same period.
  • Reading the wrong ship time. Use the created_at on the fulfillment, not the order’s updated_at, which changes for many reasons unrelated to shipping.
  • Referencing $json in the Telegram text. After the Sheets step, $json is the Sheets response. Pull the numbers from $('Compute SLA report') so the message shows the report, not the append result.

Cost at realistic volume

This workflow runs inside free tiers at normal store volume. The Shopify Admin API is included with your plan, Google Sheets is free, and Telegram messages are free. One weekly run reads a week of orders and writes a single row, which is nowhere near any rate limit.

On n8n, the cost is one execution a week that fans over the week’s orders in a single Code step. Even a store shipping thousands of orders a week runs this comfortably on n8n Cloud’s entry plans and free on self-hosted n8n. For the price of a few minutes of compute you get a fulfillment scoreboard you never have to assemble by hand.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 25 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your credentials and sheet. Prefer it done for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

What exactly counts as time-to-ship?

It is the gap between when the order was placed and when its first fulfillment was created. The report reads the order’s created_at and the created_at on its first fulfillment, then reports the difference in hours. That measures how long a paid order waited before it left your hands, which is the number your SLA is really about.

How do I change the SLA threshold or the period?

The SLA is one number in the Code node, set to 48 hours by default, so change it to your target. The period is set by the schedule and the seven-day cutoff in the same node. Move the schedule to daily or monthly and match the cutoff, and the report window follows. Both are one-line edits.

What about orders that have not shipped yet?

They are excluded on purpose. The report only measures orders that actually shipped in the window, because time-to-ship is undefined for an order still sitting unfulfilled. To watch the ones running late instead, pair this with a fulfillment delay alert that flags orders past your SLA before they ship.

Can I send the report somewhere other than Telegram and Sheets?

Yes. The Code node produces a small set of numbers, so any destination works. Swap the Telegram node for Slack, Discord, or a Gmail node, or drop the Sheets step if you only want the message. The calculation does not change, so you are only replacing the final delivery step.

Why report a median as well as an average?

A single very slow order can drag the average up and make a good week look bad. The median shows the typical order’s experience, unmoved by one outlier. Reading both tells you whether a high average is your whole operation slipping or just one stuck order, which points you at the right fix.

Related guides

Shopify VIP customer tagging by spend with n8n









Shopify VIP customer tagging by spend with n8n keeps your best customers labelled automatically, so a lifetime spend crossing your threshold turns into a VIP tag without anyone watching a spreadsheet. Once the tag exists, every other tool can read it: VIP-only emails, an automatic discount, early access to a drop. This guide builds that tagging in four n8n nodes: a daily schedule, a step that scans customers, a spend check, and a tag update on the customers who qualify.

What it does

Your highest-spending customers deserve different treatment, but Shopify will not flag them for you. Their lifetime spend sits on each customer record, and unless you sort and tag by hand every so often, that value never turns into anything you can act on. Manual tagging drifts out of date the day after you do it.

This workflow keeps the label current on a schedule. Once a day it reads your customers, checks each one’s lifetime spend against a threshold you set, and adds a VIP tag to anyone above it who is not tagged already. From then on, “VIP” is a real Shopify segment you can filter, email, and discount. A tagged customer looks like this:

Customer:      Michael Chen
Email:         michael.chen@gmail.com
Total spent:   1,240.00
Tags:          repeat, VIP

Because the check runs every morning, a customer who crosses your VIP line on Tuesday is tagged by Wednesday, with no one keeping score.

Why it beats the default

The manual default is sorting the customer list by amount spent every few weeks and tagging the top names one at a time. It is slow, it is easy to forget, and the moment a new big spender appears your segment is already wrong. Shopify has no built-in rule that tags customers by lifetime value.

An automated tag is always accurate and always ready to use. Because it lives on the customer record, every downstream tool reads it for free: email platforms segment on it, discount rules target it, and other n8n workflows can branch on it to treat VIP orders differently. Compared with Shopify Flow, which is limited to Shopify Plus, n8n runs this on any plan and lets you turn one tier into several whenever you want.

It also composes with the reporting you may already run. A customer lifetime value report tells you who your best customers are; this workflow acts on that by tagging them, so the insight becomes a segment instead of a screenshot.

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token with read and write access to customers, created through the 2026 Shopify Dev Dashboard method. New to this? Follow connect Shopify to n8n (2026 guide) first.
  • A spend threshold in mind, in your store currency, that defines a VIP.

Build time is about 15 minutes from scratch, or a couple of minutes if you import the ready-made template below and set your token and threshold.

Node-by-node list

Four nodes, one branch. Here is each one and what it does.

# Node Type Job
1 Every morning scheduleTrigger Runs the workflow once a day at a set hour so the VIP segment refreshes on its own.
2 Get customers shopify Reads your customers, paging through the full list, each carrying a total_spent and tags value.
3 Spent over threshold and not VIP? if Passes only customers whose lifetime spend clears the threshold and who are not already tagged VIP.
4 Tag customer VIP shopify Updates the customer, appending VIP to their existing tags.

How it works

  [Every morning]  (daily schedule)
          |
          v
  [Get customers]  (total_spent, tags)
          |
          v
  [Spent over threshold and not VIP?]  --- false ---> (skip)
          |
        true
          v
  [Tag customer VIP]  (append VIP to tags)
  

Step-by-step build

  1. Create a new workflow in n8n and name it “Shopify VIP customer tagging.”
  2. Add a Schedule Trigger node named “Every morning.” Set it to run daily, and pick an off-peak hour such as 6 AM so the customer scan does not compete with live traffic.
  3. Add a Shopify node named “Get customers.” Set Authentication to Access Token and attach your credential. Resource Customer, Operation Get Many, and turn on Return All so it pages through your whole customer list.
  4. Add an IF node named “Spent over threshold and not VIP?” and connect the customers node to it. Add two conditions joined with AND: first, number, {{ $json.total_spent }} is greater than or equal to 500 (change 500 to your threshold); second, string, {{ $json.tags }} does not contain VIP. Keep the loose type validation on so Shopify’s text total is read as a number.
  5. Add a Shopify node named “Tag customer VIP” and wire it to the IF node’s true output. Resource Customer, Operation Update. Set Customer ID to {{ $json.id }}. Under Update Fields add Tags with the value {{ $json.tags ? $json.tags + ', VIP' : 'VIP' }}, which appends the tag without erasing the ones already there.
  6. Save, then run the workflow once by hand to tag your existing high spenders. Check a couple of those customers in Shopify admin to confirm the VIP tag is set, then toggle the workflow Active so it keeps the segment fresh every day.
💡

Tip: want tiers instead of one label? Replace the IF with a Switch node that routes customers into Silver, Gold, and VIP bands by total_spent, each branch tagging with its own name. The customer scan and the update pattern stay identical, so a single tier grows into a full loyalty ladder with one node swap.

Common mistakes

  • Comparing spend as text. Shopify returns total_spent as a string like “500.00”. Use the number operator with loose type validation, or the threshold check compares text and behaves unexpectedly.
  • Overwriting existing tags. A customer update replaces the whole tag list. Append with {{ $json.tags ? $json.tags + ', VIP' : 'VIP' }} so a customer’s other tags survive.
  • Skipping the not-VIP condition. Without the second condition, the workflow keeps re-tagging the same people every day and can pile up duplicate labels. The notContains VIP check makes each tagging happen once.
  • Giving the token read-only access. Tagging is a write. If your Shopify access token cannot write customers, the read works but the update fails. Grant write access to customers when you create the credential.
  • Running it too often on a big store. Scanning the full customer base every few minutes is wasteful and hits rate limits. Once a day is plenty for a spend-based segment.

Cost at realistic volume

This workflow runs inside free tiers at normal store size. The Shopify Admin API is included with your plan, and one daily customer scan plus a handful of tag writes is well within rate limits. There is no third-party service to pay for, since the tag lives on Shopify.

On n8n, the cost is one run a day: read customers, filter, and update the few who newly qualify. Even a store with tens of thousands of customers runs this comfortably on n8n Cloud’s entry plans and free on self-hosted n8n. The payoff is a VIP segment that stays correct on its own, which is the input to every high-value email, discount, and perk you send next.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 15 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your token and threshold. Prefer it done for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($13) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Where does the spend figure come from?

Shopify stores a total_spent value on every customer record, which is their lifetime spend across all orders. The workflow reads that field directly, so there is no need to add up orders yourself. Compare it against your VIP threshold and the customers above it get the tag on the next run.

Won’t it re-tag the same customers every day?

No. The IF step only passes customers whose tags do not already contain VIP, so anyone already tagged is skipped. Each qualifying customer is tagged once, and the daily run simply picks up whoever has newly crossed the threshold since yesterday. No duplicate tags build up.

Can I use a different threshold or several tiers?

Yes. Change the number in the IF condition to set your VIP threshold. For tiers, use a Switch node instead of one IF, routing customers into Silver, Gold, and VIP bands by spend, each writing its own tag. The rest of the workflow stays the same, so tiering is a small change.

What can I do once customers are tagged VIP?

A tag is a segment other tools can read. Send VIP-only email campaigns, apply an automatic discount, show early access to drops, or filter the Shopify admin to VIP customers for a personal note. Other n8n workflows can also branch on the VIP tag to treat those orders differently.

Will this work on a large customer base?

Yes. The customer step pages through your full list, and the Shopify Admin API rate limits are generous for a once-a-day run. On very large stores, schedule it for an off-peak hour and, if needed, filter to customers updated recently so each run processes fewer records while still catching new VIPs.

Related guides

Shopify orders to a Notion database with n8n









Sending Shopify orders to a Notion database with n8n turns your storefront into a live order log your whole team can read, filter, and annotate, without anyone opening the Shopify admin. Each new order becomes a Notion page with the order number, customer, total, and status, ready for a kanban board, a fulfillment checklist, or a revenue rollup. This guide builds that sync in three n8n nodes: a Shopify webhook trigger, a step that maps the order fields, and a Notion create-page step.

What it does

Notion is where a lot of small teams already plan work. If your operations live in Notion, then an order sitting only in Shopify is an order your team has to leave Notion to see. Copying orders across by hand is tedious and it goes stale within minutes on a busy day.

This workflow keeps Notion current on its own. It listens for every new order through a Shopify webhook, maps the fields you care about, and creates a page in your Notion orders database. Because it is a real database, you can then flip it to a board grouped by fulfillment status, a calendar by order date, or a table filtered to unpaid orders. A row looks like this:

Order:            #1042
Customer:         Emily Rodriguez
Email:            emily.rodriguez@gmail.com
Total:            148.00
Financial status: paid
Fulfillment:      unfulfilled
Items:            3
Order date:       2026-09-01T14:30:00-04:00

Every order lands the moment it is placed, so the board your team works from is always up to date.

Why it beats the default

The manual default is exporting a Shopify CSV and pasting it into Notion, or retyping orders one by one. Both go out of date immediately and both invite typos. Shopify’s own order views are fine, but they do not live where your team plans work, and you cannot layer Notion features like a board, a checklist, or a formula on top of them.

A synced Notion database gives you Shopify’s data with Notion’s flexibility. Group orders into a fulfillment board, assign a teammate to each page, add packing notes, or build a rollup that totals today’s revenue. Compared with Shopify Flow, which is limited to Shopify Plus and cannot write to Notion, n8n does the sync on any plan and leaves room to grow into a two-way status update later.

It is also the same logging pattern that already works with Google Sheets and Airtable, just pointed at Notion. If your planning happens in Notion, this puts the order feed exactly where you will act on it.

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token, created through the 2026 Shopify Dev Dashboard method. New to connecting the two? Follow connect Shopify to n8n (2026 guide) first.
  • A Notion workspace with an internal integration token, and an orders database shared with that integration.

Build time is about 20 minutes from scratch, or a couple of minutes if you import the ready-made template below and add your credentials and database id.

Node-by-node list

Three nodes, in a straight line. Here is each one and what it does.

# Node Type Job
1 New Shopify order shopifyTrigger Fires on the orders/create webhook for every new order and passes the full order payload downstream.
2 Map order fields set Flattens the order into clean fields: order number, customer, email, total as a number, statuses, item count, and date.
3 Create Notion page notion Creates a page in your Notion orders database, filling each property from the mapped fields.

The Notion database

Create a Notion database first, because the property names and types in n8n must match it exactly. This template writes to these properties.

Property Notion type Example
Order Title #1042
Customer Text Emily Rodriguez
Email Text emily.rodriguez@gmail.com
Financial status Text paid
Fulfillment status Text unfulfilled
Total Number 148.00
Items Number 3
Order date Text 2026-09-01T14:30:00-04:00
📌

Note: property names are case-sensitive and must match the template exactly. Total and Items are Number properties so Notion can sum and sort them. If you prefer a true date column or a status dropdown, change those properties to Date or Select in Notion and switch the matching node fields to the same type.

How it works

  [New Shopify order]  (orders/create webhook)
          |
          v
  [Map order fields]  (order #, customer, total number, statuses, date)
          |
          v
  [Create Notion page]  (new row in your orders database)
  

Step-by-step build

  1. Create a new workflow in n8n and name it “Shopify orders to Notion.”
  2. Add a Shopify Trigger node. Set Authentication to Access Token and attach your Shopify Admin API credential. Set the Topic to orders/create. When you activate the workflow later, n8n registers this webhook in your store for you.
  3. In Notion, create the orders database with the eight properties in the table above. Then create an internal integration (Settings, then Connections, then manage integrations), copy its token, open the database’s three-dot menu, choose Connections, and add the integration so it can write there.
  4. Back in n8n, add a Set (Edit Fields) node named “Map order fields” and connect the trigger to it. Add these assignments: orderName = {{ $json.name }}, customerName = {{ $json.customer ? $json.customer.first_name + ' ' + $json.customer.last_name : 'Guest checkout' }}, email = {{ $json.email || ($json.customer ? $json.customer.email : '') }}, financialStatus = {{ $json.financial_status || 'pending' }}, fulfillmentStatus = {{ $json.fulfillment_status || 'unfulfilled' }}, total (number) = {{ $json.total_price }}, itemCount (number) = {{ $json.line_items ? $json.line_items.length : 0 }}, and createdAt = {{ $json.created_at }}. Setting total to the Number type converts Shopify’s string price into a real number for Notion.
  5. Add a Notion node named “Create Notion page” after the Set node. Resource Database Page, Operation Create. For Database, pick your orders database (or paste its id). Then add one property value per column: map Order (title) to {{ $json.orderName }}, Customer to {{ $json.customerName }}, Email to {{ $json.email }}, Financial status to {{ $json.financialStatus }}, Fulfillment status to {{ $json.fulfillmentStatus }}, Total (number) to {{ $json.total }}, Items (number) to {{ $json.itemCount }}, and Order date to {{ $json.createdAt }}.
  6. Save, then toggle the workflow to Active so the webhook registers. Place a test order and confirm a new page appears in your Notion database with every field filled.
💡

Tip: want to avoid any chance of a duplicate row when Shopify retries a webhook? Add a Notion “Get Many” (search) step before the create, filtered to the order number, and an IF that only creates the page when none is found. Store the order number in a text property so that lookup has a reliable key.

Common mistakes

  • Property names that do not match. Notion property keys are case-sensitive. If the node says Total and your database column is total, the value silently goes nowhere. Copy names exactly.
  • Forgetting to share the database with the integration. A fresh integration token can see nothing until you add it to the database through the Connections menu. Without that, the create step returns a permissions error.
  • Storing the total as text. If Total is a Text property, Notion cannot sum or sort it. Keep it a Number property and keep the mapping step’s Number type so revenue math works.
  • Leaving the workflow inactive. The orders/create webhook only exists while the workflow is Active. No pages appearing usually means the Active toggle is off.
  • Expecting a missing customer to always be present. Guest checkouts can arrive without a customer object, so the mapping falls back to “Guest checkout” and the store customer email, which keeps the page from failing.

Cost at realistic volume

This workflow runs inside free tiers at normal store volume. The Notion API is free on every plan, and an order log is well within workspace block limits. The Shopify Admin API is included with your plan, and the webhook costs nothing on the Shopify side.

On n8n, each order runs three quick steps. A store doing 800 orders a month runs about 800 short executions, which sits inside n8n Cloud’s entry plans and is free on self-hosted n8n. The practical payoff is a Notion order board that is always current, replacing the CSV export and paste that would otherwise eat time every day.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 20 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your credentials and database id. Prefer it done for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($12) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Do I need a paid Notion plan for this?

No. A free Notion plan handles this, and the Notion API integration is free on every plan. You create one internal integration, share your orders database with it, and n8n uses that token to add pages. The only real limit is the number of blocks in a workspace, which an order log will not reach for a very long time.

How do I connect Notion to n8n?

In Notion, open Settings, then Connections, then Develop or manage integrations, and create a new internal integration to get a token. Paste that token into a Notion credential in n8n. Then open your orders database, click the three-dot menu, choose Connections, and add your integration so it can write to that database.

Will it create duplicate rows if Shopify retries the webhook?

Shopify can retry a webhook if it does not get a fast response, which could create a second row. To be safe, store the Shopify order id in a text property and, before creating, search the database for that id and skip if it already exists. The template keeps the base flow simple, and the guide shows where to add that check.

Can I update the Notion page when the order ships?

Yes. Build a second small workflow on the orders/fulfilled webhook that searches the database for the order and uses the Notion Update operation to set the fulfillment status. Because the first workflow stores the order number, the second one has a reliable key to find the right page and update it in place.

Why store the total as a Number and not text?

A Number property lets Notion sum a column, sort by value, and drive rollups and formulas. If you store the total as text, none of that works and you get a plain string. The mapping step converts the Shopify total to a number before it reaches Notion, so your database can total revenue and filter by order size.

Related guides

Shopify order alerts to Discord with n8n









Shopify order alerts to Discord with n8n give your team a live sales feed in the app they already keep open all day. Instead of refreshing the Shopify admin or waiting on the mobile push, every new order drops into a channel the moment it is placed, with the order number, customer, total, and item count. This guide builds that alert in three n8n nodes: a Shopify webhook trigger, a step that formats the message, and a Discord post. No bot to build and no code to write.

What it does

Plenty of small teams run their whole day inside Discord. Support, fulfillment, and the founder are all in one server, so that is where a new-order ping actually gets seen. Shopify does not post to Discord on its own, and the built-in order notifications are email or the mobile app, neither of which lands where the team is talking.

This workflow closes that gap. It listens for every new order through a Shopify webhook, formats a short readable message, and posts it to a Discord channel of your choice. A message looks like this in the channel:

🛒 New order #1042
Customer: Emily Rodriguez
Total: 148.00 USD
Items: 3

Everyone watching the channel sees the sale as it happens. No one has to be logged into Shopify, and there is no polling delay, because the webhook fires the instant the order is created.

Why it beats the default

Shopify’s own order alerts are an email to the store owner and a push from the Shopify mobile app. Email gets buried, and the mobile push only reaches whoever installed the app and left notifications on. Neither reaches a shared team space, so the person who needs to act on the order often hears about it last.

A Discord channel is shared by default. The whole team sees the same feed, you can react with an emoji to claim an order, and the history is searchable. Compared with Shopify Flow, which is limited to Shopify Plus and cannot post to Discord, n8n does the routing on any plan and leaves room to grow. You can later add a value filter, split alerts across channels, or attach line-item detail without rebuilding anything.

Because the connection is a plain Discord webhook, there is nothing to maintain. No bot token to rotate, no server permissions to manage, and no extra app installed in your Discord. It is the lightest possible way to get Shopify sales into a team channel.

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token, created through the 2026 Shopify Dev Dashboard method. New to connecting the two? Follow connect Shopify to n8n (2026 guide) first, then return here.
  • A Discord server where you can edit a channel, so you can create a webhook.

Build time is about 15 minutes from scratch, or a couple of minutes if you import the ready-made template below and paste in your access token and webhook URL.

Node-by-node list

Three nodes, in a straight line. Here is each one and what it does.

# Node Type Job
1 New Shopify order shopifyTrigger Fires on the orders/create webhook for every new order and hands the full order payload to the next node.
2 Build Discord message set Pulls out the order number, customer name, total, and item count, and assembles the formatted discordMessage string.
3 Post to Discord discord Posts the message to your channel through a Discord webhook. No bot needed.

How it works

  [New Shopify order]  (orders/create webhook)
          |
          v
  [Build Discord message]  (order #, customer, total, items)
          |
          v
  [Post to Discord]  (webhook -> your channel)
  

Step-by-step build

  1. Create a new workflow in n8n and name it “Shopify order alerts to Discord.”
  2. Add a Shopify Trigger node. Set Authentication to Access Token and attach your Shopify Admin API credential. Set the Topic to orders/create. When you activate the workflow later, n8n registers this webhook in your store for you, so there is nothing to paste into Shopify by hand.
  3. In Discord, open the channel you want alerts in, choose Edit Channel, then Integrations, then Webhooks, and click New Webhook. Give it a name like “Shopify orders,” confirm the channel, and copy the webhook URL.
  4. Back in n8n, add a Set (Edit Fields) node named “Build Discord message” and connect the trigger to it. Add these assignments: orderName = {{ $json.name }}, customerName = {{ $json.customer ? $json.customer.first_name + ' ' + $json.customer.last_name : 'Guest checkout' }}, total = {{ $json.total_price }} {{ $json.currency }}, itemCount (number) = {{ $json.line_items ? $json.line_items.length : 0 }}, and a string discordMessage that stitches them together:
    🛒 **New order {{ $json.name }}**
    Customer: {{ $json.customer ? $json.customer.first_name + ' ' + $json.customer.last_name : 'Guest checkout' }}
    Total: **{{ $json.total_price }} {{ $json.currency }}**
    Items: {{ $json.line_items ? $json.line_items.length : 0 }}

    The double asterisks are Discord markdown, so the order number and total render in bold.

  5. Add a Discord node named “Post to Discord” after the Set node. Set Authentication to Webhook and create a Discord Webhook credential, pasting the URL you copied in step 3. Set Message (content) to {{ $json.discordMessage }}. Because the Set node is directly upstream, $json here already holds your prepared message.
  6. Save, then toggle the workflow to Active so the webhook registers. Place a test order in your store and watch the channel for the alert.
💡

Tip: send only the orders that matter by dropping an IF node between the trigger and the message builder. A condition of {{ $json.total_price }} greater than 200 turns this into a high-value order alert, so a muted all-orders channel and a noisy VIP channel can run from the same store.

Common mistakes

  • Using bot authentication by habit. For posting to one channel, the Webhook authentication is far simpler than a bot token, and it needs no server permissions. Pick Webhook unless you specifically need bot features.
  • Pasting the webhook URL into the message instead of the credential. The URL is the destination, and it belongs in the Discord Webhook credential, not in the content field.
  • Forgetting to activate the workflow. The orders/create webhook is only registered while the workflow is Active. If test orders produce no message, check the Active toggle first.
  • Assuming a customer always exists. Guest checkouts can arrive without a customer object. The expressions above fall back to “Guest checkout” so the message never breaks on a missing name.
  • Expecting markdown to render everywhere. Bold and italics work in the message body, but Discord will not render HTML. Keep formatting to Discord markdown, or move to an embed for a structured card.

Cost at realistic volume

This workflow runs inside free tiers at any normal store volume. Discord webhooks are free and generous, with rate limits far above what a store’s order flow will hit. The Shopify Admin API is included with your plan, and the webhook trigger costs you nothing on the Shopify side.

On n8n, each order runs three quick steps. A store doing 1,000 orders a month runs about 1,000 short executions, which sits comfortably inside n8n Cloud’s entry plans and is free on self-hosted n8n. In practice this is a zero-cost alert that replaces the habit of refreshing the Shopify admin to see whether a sale came in.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 15 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your token and webhook URL. Want it handled for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($9) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Do I need a Discord bot to send order alerts?

No. This template uses a Discord channel webhook, which is a URL you generate inside the channel settings in under a minute. There is no bot to create, no server-wide permissions to grant, and nothing to host. n8n simply posts to that webhook URL, and Discord shows the message in the channel you picked.

Where do I get the Discord webhook URL?

Open the target channel in Discord, go to Edit Channel, then Integrations, then Webhooks, and click New Webhook. Name it, pick the channel, and copy the webhook URL. In n8n you paste that URL into a Discord Webhook credential, and the node uses it to post. Keep the URL private, since anyone with it can post to your channel.

Will this spam my channel on a busy sales day?

Every new order posts one message, so a high-volume store can get chatty. If that is too much, add an IF node after the trigger to only alert on orders above a value threshold, or route alerts to a dedicated low-traffic channel. Many stores keep an all-orders channel muted and a high-value channel with notifications on.

Can I alert only on high-value orders?

Yes. Insert an IF node between the trigger and the message builder, with a condition like total price greater than 200. Only orders that pass reach the Discord node. This is a one-node change and does not touch the rest of the flow, so you can add or remove the filter whenever your alert needs shift.

Can I show line items or a richer card in Discord?

Yes. Discord supports embeds, which render as a titled card with fields and color. The Discord node exposes an embeds option where you can add the order number as the title and line items, shipping city, or payment status as fields. The template ships with a clean text message you can extend into an embed whenever you want.

Related guides

Shopify wholesale B2B order routing with n8n









Shopify wholesale B2B order routing with n8n solves a quiet problem that grows with every new stockist you sign: wholesale orders look identical to retail orders in your admin, so they sit in the same queue and get packed the same way. This guide builds a small n8n workflow that spots an order from a wholesale-tagged customer the moment it lands, adds a wholesale-queue tag, and pings your B2B fulfillment team on Telegram so those orders never slip into the retail pile.

What it does

When a store sells to both consumers and trade buyers, the two order types need different handling. Wholesale orders often ship on account terms, use different packing slips, get palletized instead of boxed, and route to a separate person. On a stock Shopify plan there is no built-in switch that says “this one is trade, treat it differently.”

This workflow adds that switch. It listens for every new order through a Shopify webhook. For each order it reads the buyer’s customer tags. If the buyer is tagged as a wholesale account, n8n stamps the order with a wholesale-queue tag and sends a Telegram message to your B2B channel with the order number, the customer name, and the total. Retail orders pass straight through and are ignored, so your consumer fulfillment stays exactly as it is today.

The result is a clean separation you can filter on. In Shopify admin you can search tag:wholesale-queue to see every open trade order, and your warehouse lead gets a real-time nudge instead of scrolling the full order list hunting for the B2B ones.

Why it beats the default

The manual default is a person remembering which customers are wholesale and eyeballing each order. That works at five trade accounts and breaks at fifty. Someone is out sick, a new hire does not know the account list, and a pallet order gets packed as twelve separate retail parcels. The cost of a missed classification is real: wrong packing, wrong paperwork, and a trade customer who now doubts you.

Shopify Flow can tag orders, but it is limited to Shopify Plus and keeps the logic locked inside Shopify. Routing a notification to Telegram, layering in Google Sheets logging later, or fanning out to a supplier email means leaving Flow anyway. n8n gives you the tag plus the alert plus room to grow in one place, on any Shopify plan, self-hosted or cloud.

Because the decision is driven by a customer tag you already control, onboarding a new stockist is a single action: tag their account wholesale. From that point every order they place is routed correctly with no change to the workflow and no tribal knowledge required.

What you need

  • A Shopify store on any plan, with an admin login that can create a custom app.
  • An n8n instance, either n8n Cloud or self-hosted (version 1.0 or newer).
  • A Shopify Admin API access token, created through the 2026 Shopify Dev Dashboard method. If you have not connected Shopify to n8n before, follow connect Shopify to n8n (2026 guide) first, then come back.
  • A Telegram bot token and the chat ID of the channel or group your B2B team watches.
  • Your wholesale customers tagged with wholesale in Shopify (Customers, open the account, add the tag).

Build time is around 20 minutes from scratch, or a couple of minutes if you import the ready-made template below and drop in your credentials.

Node-by-node list

The workflow is five nodes in a single line with one branch. Here is what each one is and does.

# Node Type Job
1 New Shopify order shopifyTrigger Fires on the orders/create webhook for every new order, delivering the full order payload including the customer object.
2 Customer tagged wholesale? if Checks whether customer.tags contains the word wholesale. True routes onward, false ends the run.
3 Prep wholesale fields set Pulls out the order id, order name, customer name, and total, and builds the appended tag string.
4 Tag order wholesale-queue shopify Updates the order, writing back the existing tags plus wholesale-queue.
5 Notify wholesale queue telegram Sends a formatted alert to your B2B team channel with the order details.

How it works

  [New Shopify order]  (orders/create webhook)
          |
          v
  [Customer tagged wholesale?]  --- false ---> (ignored, retail)
          |
        true
          v
  [Prep wholesale fields]  (order id, name, total, appended tags)
          |
          v
  [Tag order wholesale-queue]  (Shopify order update)
          |
          v
  [Notify wholesale queue]  (Telegram alert to B2B team)
  

Step-by-step build

  1. Create a new workflow in n8n and name it something like “Shopify wholesale B2B order routing.”
  2. Add a Shopify Trigger node. Set Authentication to Access Token and attach your Shopify Admin API credential. Set the Topic to orders/create. When you activate the workflow later, n8n registers this webhook in your store automatically, so there is nothing to paste into Shopify by hand.
  3. Add an IF node named “Customer tagged wholesale?” and connect the trigger to it. Add one condition: left value {{ $json.customer.tags }}, operator String contains, right value wholesale. Turn on the loose type validation and case-insensitive option so Wholesale and WHOLESALE also match.
  4. Add a Set (Edit Fields) node named “Prep wholesale fields” and wire it to the IF node’s true output. Create these string assignments: orderId = {{ $json.id }}, orderName = {{ $json.name }}, customerName = {{ $json.customer.first_name }} {{ $json.customer.last_name }}, total = {{ $json.total_price }} {{ $json.currency }}, and newTags = {{ $json.tags ? $json.tags + ', wholesale-queue' : 'wholesale-queue' }}. That last expression appends the queue tag without wiping any existing order tags.
  5. Add a Shopify node named “Tag order wholesale-queue” after the Set node. Resource Order, Operation Update. Set Order ID to {{ $json.orderId }}. Under Update Fields add Tags with the value {{ $json.newTags }}. Use Admin API version 2026-04 on the credential so the order update behaves as documented.
  6. Add a Telegram node named “Notify wholesale queue” as the last step. Attach your Telegram bot credential, set Chat ID to your B2B channel id, and set the Text to a short message that references the earlier node, for example:
    New wholesale order {{ $('Prep wholesale fields').item.json.orderName }}
    Customer: {{ $('Prep wholesale fields').item.json.customerName }}
    Total: {{ $('Prep wholesale fields').item.json.total }}
    Tagged wholesale-queue for the B2B fulfillment team.

    Referencing the Set node by name matters here, because after the Shopify update step the current $json is the Shopify response, not your prepared fields.

  7. Save, then toggle the workflow to Active. Place a test order from a customer account tagged wholesale and confirm the order picks up the wholesale-queue tag and the Telegram message arrives.
💡

Tip: prefer a schedule instead of a live webhook? Swap node 1 for a Schedule Trigger plus a Shopify “Get Many” orders step filtered to recent, unfulfilled orders. The rest of the workflow stays the same. The webhook version is lighter and near instant, so it is the better default for routing.

Common mistakes

  • Reading the wrong tags. {{ $json.tags }} is the order tag string, while {{ $json.customer.tags }} is the customer tag string. The routing decision must read the customer tags, or brand-new trade accounts never match.
  • Overwriting order tags. A Shopify order update replaces the entire tag list. If you set Tags to just wholesale-queue you erase anything already there. Always append, as the Set node does.
  • Referencing $json in the Telegram text. After the Shopify update, $json is the Shopify order response. Pull your display fields from $('Prep wholesale fields') instead so the message shows what you prepared.
  • Guest checkouts with no customer. A rare order can arrive without a customer object. The contains check treats a missing value as no match, so guest orders route to retail, which is the safe default here.
  • Leaving the workflow inactive. The orders/create webhook is only registered while the workflow is Active. If nothing fires, check the Active toggle first.

Cost at realistic volume

Everything in this workflow sits inside free tiers at normal store volume. Telegram bot messages are free with no cap that a store would reach. The Shopify Admin API is included with your plan and rate limits are generous for one tag write per wholesale order.

On n8n, one order equals at most two executed steps that count against usage: the webhook plus the update and alert for wholesale orders, and effectively nothing for retail orders since they exit at the IF node. A store doing 60 wholesale orders a month runs about 60 execution paths. On n8n Cloud’s entry plans that is a rounding error, and on self-hosted n8n it is free. Realistically this workflow costs you nothing to run and saves the minutes per order a person would otherwise spend sorting trade from retail.

Download the ready-to-import template

The guide above is free to follow, and building it by hand takes about 20 minutes. The template is the same validated workflow as a single .json import, so you skip the build and just add your credentials. Prefer it done for you? Our done-for-you setup service installs and tests it on your instance.

Download the template ($13) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Do I need Shopify Plus or a B2B plan for this?

No. This routing runs entirely on customer tags and the standard Admin API, so any Shopify plan works. You mark wholesale accounts with a customer tag like wholesale, and n8n reacts to that tag on every new order. You do not need the native B2B channel or Shopify Plus company profiles for it to function.

How does n8n know a customer is a wholesale buyer?

The orders/create webhook payload includes the customer object, and that object carries the customer tags string. The IF node checks whether that string contains the word wholesale. If you tag your B2B accounts with wholesale in Shopify admin, every order they place is detected automatically, with no manual lookup on your side.

Will tagging the order overwrite existing order tags?

Not with this template. The Shopify order update replaces the full tag list, so the Set node first reads the order’s current tags and appends wholesale-queue to them. A guest order with no tags simply gets wholesale-queue, and an order that already carries tags keeps them all plus the new one.

Can I send the alert to Slack or email instead of Telegram?

Yes. Swap the final Telegram node for a Slack or Gmail node and map the same fields the Set node prepared: order name, customer name, and total. The routing and tagging logic upstream does not change, so you only replace the last notification step to fit whichever channel your fulfillment team already watches.

What happens to regular retail orders?

Nothing. The IF node has two outputs, and retail orders leave through the false branch, which is not connected to anything. They flow through your normal fulfillment untouched. Only orders from customers tagged wholesale get the wholesale-queue tag and trigger the Telegram alert to your B2B team.

Related guides

Shopify Unfulfilled Orders Daily Digest with n8n

A Shopify unfulfilled orders daily digest is a scheduled n8n workflow that checks your store every morning, finds every order that has been sitting open for more than 24 hours, and emails you one clean summary. Instead of scrolling the Orders tab to see what slipped, you get a single message with the order name, customer, item count, value, and how long each one has been waiting. This guide builds it from scratch and gives you the ready-to-import template.

What it does

Every store leaks a few orders. A payment clears late, a shipment is half-picked, a note gets missed, and an order sits unfulfilled while the customer waits and wonders. By the time you notice, the order is two or three days old and the review risk is real. This workflow turns that from a thing you have to remember into a thing that arrives in your inbox.

Once a day at 8:00, the workflow asks Shopify for open orders that are still marked unshipped and were created more than 24 hours ago. It formats them into a short list and sends you an email digest. If everything is fulfilled, you get a one-line all-clear so you know the check actually ran. No dashboards to open, no app to babysit, just a daily line of sight into the orders that need a human.

Why it beats the default

Shopify already shows unfulfilled orders in the admin, and it can email you on every new order. Neither of those solves the real problem. The Orders tab needs you to remember to look, and per-order emails train you to ignore them because most orders are fine. What you actually want to know is the exception: which orders are late right now.

A daily digest is the right shape for that. It batches, so you get one message instead of fifty. It filters by age, so a brand-new order placed an hour ago never nags you. And it is a pull, not a push, so it keeps working even if a Shopify notification setting gets toggled off. The result is a report you will actually read, once a day, that maps exactly to the work sitting in your queue.

This is the digest counterpart to a per-order fulfillment delay alert. Use the alert when you want an instant ping the moment one order goes late; use this digest when you want a calm once-a-day roll-up of everything still open.

What you need

  • An n8n instance (cloud or self-hosted, version 1.0 or newer).
  • A Shopify custom app access token with read_orders scope. If you have not connected Shopify to n8n yet, follow the 2026 Dev Dashboard connection guide first, then come back.
  • A Gmail account (or any email node) for delivery. Swap in Telegram or Slack if you prefer.

Build time is about 20 minutes from scratch, or under 5 minutes if you import the template at the end and just attach your two credentials.

Node-by-node list

Five nodes, one straight line, no branches. Here is the whole thing before we configure it:

[Daily 08:00 schedule] -> [Cutoff timestamp (24h ago)] -> [Get unfulfilled orders]
        -> [Build digest] -> [Send digest email]
  
  1. Daily 08:00 schedule (Schedule Trigger) fires the workflow once a day.
  2. Cutoff timestamp (24h ago) (Set) calculates the moment 24 hours in the past.
  3. Get unfulfilled orders (HTTP Request) pulls open, unshipped orders created before that cutoff from the Shopify Admin API.
  4. Build digest (Code) turns the order list into a readable summary and a count.
  5. Send digest email (Gmail) delivers the summary to your inbox.

Step-by-step build

1. Daily 08:00 schedule (Schedule Trigger)

Add a Schedule Trigger node. Set the rule to a Cron expression of 0 8 * * * so it runs at 08:00 in your instance timezone. Pick whatever hour lets you act on the list before you get busy. If you want two checks, add a second cron line such as 0 14 * * * for an afternoon sweep.

2. Cutoff timestamp (24h ago) (Set)

Add a Set node. Create one assignment named cutoff, type String, with this expression as the value:

={{ $now.minus({ hours: 24 }).toISO() }}

This produces an ISO 8601 timestamp for exactly one day ago, for example 2026-08-30T08:00:00.000-05:00. Shopify’s API accepts that format directly. To digest older orders, change 24 to 48; to catch same-day stragglers, drop it to 12.

3. Get unfulfilled orders (HTTP Request)

Add an HTTP Request node. Configure it like this:

  • Method: GET
  • URL: https://YOUR_STORE.myshopify.com/admin/api/2026-04/orders.json
  • Authentication: Predefined Credential Type, then Shopify Access Token API, and select your credential.
  • Send Query Parameters: on. Add these five:
Parameter Value Why
status open Ignore archived and cancelled orders
fulfillment_status unshipped Only orders with nothing fulfilled yet
created_at_max ={{ $json.cutoff }} Only orders older than 24 hours
limit 250 Maximum orders per page
fields id,name,created_at,total_price,currency,customer,line_items Return only the fields the digest uses

The response is a JSON object with an orders array. A single unfulfilled order looks roughly like this:

{
  "id": 5541203112,
  "name": "#1042",
  "created_at": "2026-08-29T15:22:10-05:00",
  "total_price": "84.00",
  "currency": "USD",
  "customer": { "first_name": "Emily", "last_name": "Rodriguez" },
  "line_items": [ { "quantity": 2 } ]
}
Note: Shopify’s unshipped value means “nothing fulfilled yet”. Orders that are partially fulfilled use partial. If you want those flagged too, run the request twice or use fulfillment_status=any and filter in the next node.

4. Build digest (Code)

Add a Code node in Run Once for All Items mode. It reads the orders array, keeps the unfulfilled ones, and builds one summary line per order plus a subject and a count.

const resp = $input.first().json;
const orders = Array.isArray(resp.orders) ? resp.orders : [];

// Keep only orders that are still unfulfilled (fulfillment_status null/empty).
const stale = orders.filter(o => !o.fulfillment_status);

const lines = stale.map(o => {
  const created = new Date(o.created_at);
  const hours = Math.floor((Date.now() - created.getTime()) / 3600000);
  const name = o.name || ('#' + o.id);
  const cust = o.customer
    ? ((o.customer.first_name || '') + ' ' + (o.customer.last_name || '')).trim() || 'Guest'
    : 'Guest';
  const units = Array.isArray(o.line_items)
    ? o.line_items.reduce((s, li) => s + (li.quantity || 0), 0)
    : 0;
  const money = (o.total_price || '0') + ' ' + (o.currency || '');
  return `${name}  |  ${cust}  |  ${units} item(s)  |  ${money.trim()}  |  waiting ${hours}h`;
});

const count = stale.length;
const subject = count > 0
  ? `Shopify: ${count} unfulfilled order(s) over 24h`
  : 'Shopify: all orders fulfilled, nothing waiting';

const digest = count > 0
  ? `You have ${count} order(s) unfulfilled for more than 24 hours:\n\n` + lines.join('\n')
  : 'Good news: every open order is fulfilled. Nothing has been waiting more than 24 hours.';

return [{ json: { count, subject, digest } }];

Because the node always returns one item, the email always sends, even on a clean day. That is what gives you the all-clear message and confirms the check ran.

5. Send digest email (Gmail)

Add a Gmail node set to Message, Send. Fill in:

  • To: your own address (or an ops alias).
  • Subject: ={{ $json.subject }}
  • Email Type: Text.
  • Message: ={{ $json.digest }}

Save the workflow and toggle it Active. A finished digest lands like this:

Subject: Shopify: 3 unfulfilled order(s) over 24h

You have 3 order(s) unfulfilled for more than 24 hours:

#1042  |  Emily Rodriguez  |  2 item(s)  |  84.00 USD  |  waiting 27h
#1039  |  Michael Chen  |  1 item(s)  |  49.00 USD  |  waiting 41h
#1035  |  Sarah Thompson  |  4 item(s)  |  156.00 USD  |  waiting 63h

Common mistakes

  • Wrong fulfillment value. The API uses unshipped, not unfulfilled, in the query string. Using the wrong word returns everything and floods the digest.
  • Timezone drift on the cron. The Schedule Trigger runs in your instance timezone. Set the timezone in the workflow settings so 8:00 means 8:00 where you are, not UTC.
  • Reading the array wrong in Code. The Shopify response wraps orders in an orders key. Reference resp.orders, not the top-level object, or the loop finds nothing.
  • Missing read scope. If the request returns a 401 or 403, your access token lacks read_orders. Regenerate it with that scope from the Shopify Dev Dashboard.
  • Assuming empty means broken. If your store genuinely has no late orders, you get the all-clear, not an error. That is success.

Cost at realistic volume

This workflow is effectively free to run. It executes once a day, which is 30 executions a month, well inside every n8n plan including self-hosted. Each run makes a single Shopify API call and one Gmail send, so you never approach Shopify’s rate limits or Gmail’s daily send cap. There is no AI model call, no paid third-party service, and no per-message fee anywhere in the chain. If you add a second daily run, you are still at 60 executions a month. The only real cost is the 20 minutes to build it, which the template removes.

Ready-to-import template

The guide above is free to follow, start to finish. If you would rather not build it by hand, the downloadable template is the exact five-node workflow, validated and ready to import. Attach your Shopify and Gmail credentials, set your email address, and switch it on. Want it done for you end to end? See our done-for-you setup service.

Download the template ($13) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

How does the workflow know an order is unfulfilled for more than 24 hours?

The Set node computes a cutoff timestamp 24 hours before the run. The Shopify request then asks only for open orders with fulfillment_status unshipped that were created before that cutoff, so nothing newer than a day ever slips into the digest.

Will I get an email when there are no unfulfilled orders?

Yes. The Code node always returns one item, so the Gmail node always sends. When nothing is waiting you get a short all-clear message. If you prefer silence on quiet days, add an IF node after Build digest that only sends when the count is greater than zero.

Can I send the digest to Telegram or Slack instead of Gmail?

Yes. Replace the Gmail node with a Telegram or Slack node and map the same digest text into the message field. The Code node output does not change, so the rest of the workflow stays exactly as it is. Telegram is a good fit for a quick phone glance.

What if I have more than 250 unfulfilled orders?

The request caps at 250 orders per page, which covers almost every store for a 24-hour backlog. If you routinely exceed that, enable pagination on the HTTP Request node or shorten the age window so the digest stays readable and the page limit is never reached.

Does this change anything in my Shopify store?

No. The workflow only reads orders through the Admin API and sends you an email. It never edits, fulfills, or cancels anything, so it is safe to run daily. The single action it takes is delivering the summary to your inbox each morning.

Related guides

How to build a Shopify sales channel report with n8n









A Shopify sales channel report built in n8n tells you exactly where last week’s revenue came from, whether that is your online store, Shopify POS, draft-order invoices, Instagram, or Google, without opening a single analytics dashboard. This guide builds a workflow that pulls the past seven days of orders every Monday morning, groups them by order source, writes one row per channel to Google Sheets, and emails you a ranked summary. The whole build takes about 30 minutes.

What it does

Every Shopify order carries a source_name field that records the sales channel it came from: web for the online store, pos for in-person sales, shopify_draft_order for invoices you send by hand, and channel handles like instagram or google for connected surfaces. Individually those values are buried inside each order. Rolled up, they answer the question every store owner actually asks on Monday: which channel made me money last week?

This workflow runs on a weekly schedule and does four things in one pass:

  1. Fetches all Shopify orders from the last seven days, including POS and draft orders.
  2. Groups them by source_name, counting orders and summing revenue per channel.
  3. Appends one row per channel to a Google Sheet so you build a week-over-week history.
  4. Emails you a single ranked table, sorted by revenue, with each channel’s share of the total.

You get a clean read on channel mix, a permanent Sheet you can chart later, and zero manual exporting. It connects Shopify, n8n, Google Sheets, and Gmail. If you have not linked your store to n8n yet, start with our guide on connecting Shopify to n8n in 2026, then come back here.

Why it beats the default

Shopify’s built-in Analytics does have a “Sales by channel” report, but it lives behind a login, resets to a default date range, and cannot be pushed anywhere. You cannot email it to a co-founder on a schedule, you cannot append it to a spreadsheet you already use for forecasting, and on the Basic plan several breakdowns are locked entirely.

Doing this in n8n flips all of that:

  • The report comes to you. It lands in your inbox every Monday at 7am, ranked and ready, before you have opened a browser tab.
  • You keep the history. Each run appends rows to Google Sheets, so after a month you can chart whether Instagram is trending up or your draft-order revenue is drying out. Shopify’s dashboard only shows the window you pick.
  • It is yours to extend. Want to split by channel and month, or flag any week where POS drops below a threshold? It is a Code node and an IF node away, not a plan upgrade.

What you need

  • An n8n instance (Cloud or self-hosted). Both work; nothing here is version-specific beyond n8n 1.0+.
  • A Shopify custom app access token with read_orders scope, created through the Shopify Dev Dashboard method covered in the connection guide. This workflow reads orders through the Admin API (version 2026-04) and never writes to your store.
  • A Google account for Google Sheets and Gmail, connected to n8n by OAuth2.
  • One blank Google Sheet with a header row (columns listed in the build below).

Estimated build time: about 30 minutes from scratch, or under 10 minutes if you import the ready-made template at the end of this guide.

Node-by-node list

Six nodes, wired in a single straight line. No branches, which keeps it easy to debug.

[Every Monday 07:00]  (Schedule Trigger)
        |
        v
[Get last 7 days of orders]  (Shopify - Get Many)
        |
        v
[Group orders by channel]  (Code)
        |
        v
[Append rows to Sheet]  (Google Sheets - Append)
        |
        v
[Keep one for email]  (Limit - maxItems 1)
        |
        v
[Email the report]  (Gmail - Send)
  
# Node Type Job
1 Every Monday 07:00 Schedule Trigger Fires once a week
2 Get last 7 days of orders Shopify Pulls all recent orders
3 Group orders by channel Code Aggregates by source_name
4 Append rows to Sheet Google Sheets Logs one row per channel
5 Keep one for email Limit Reduces to a single item
6 Email the report Gmail Sends the ranked table

Step-by-step build

1. Add the Schedule Trigger

Create a new workflow and add a Schedule Trigger. Set the interval to Weeks, every 1 week, trigger day Monday, at hour 7. That gives you a fresh report waiting each Monday morning. Pick whatever hour suits your timezone; the n8n instance timezone controls when 7 actually fires.

2. Fetch the last 7 days of orders

Add a Shopify node. Set Resource to Order and Operation to Get Many, then turn on Return All so pagination is handled for you. Under Options, add:

  • Created At Min: ={{ $now.minus(7, 'days').toISO() }}
  • Status: any (so cancelled and closed orders are included, matching Shopify’s own revenue view)
  • Fields: id,total_price,source_name,created_at,financial_status (smaller payload, faster run)

Attach your Shopify credential. Each returned item looks roughly like this:

{
  "id": 5218841116,
  "total_price": "129.00",
  "source_name": "web",
  "created_at": "2026-08-25T14:31:07-05:00",
  "financial_status": "paid"
}

3. Group the orders by channel

Add a Code node (Run Once for All Items) and paste the logic below. It reads every incoming order, buckets it by source_name, sums total_price, and returns one item per channel, sorted by revenue. It also builds the HTML email table once and carries it on each item.

const orders = $input.all().map(i => i.json);
const weekOf = $now.minus(7, 'days').toFormat('yyyy-LL-dd');
const generatedAt = $now.toISO();

const map = {};
for (const o of orders) {
  const channel = (o.source_name && String(o.source_name).trim()) || 'unknown';
  const amount = parseFloat(o.total_price || '0') || 0;
  if (!map[channel]) map[channel] = { channel, order_count: 0, revenue: 0 };
  map[channel].order_count += 1;
  map[channel].revenue += amount;
}

const channels = Object.values(map).sort((a, b) => b.revenue - a.revenue);
for (const c of channels) c.revenue = Math.round(c.revenue * 100) / 100;

const totalOrders = channels.reduce((s, c) => s + c.order_count, 0);
const totalRevenue = Math.round(channels.reduce((s, c) => s + c.revenue, 0) * 100) / 100;

const rows = channels.map(c => {
  const share = totalRevenue > 0 ? Math.round((c.revenue / totalRevenue) * 1000) / 10 : 0;
  return `<tr><td>${c.channel}</td><td>${c.order_count}</td><td>${c.revenue.toFixed(2)}</td><td>${share}%</td></tr>`;
}).join('');

const emailHtml = `<h2>Sales channel report — week of ${weekOf}</h2>
<p>${totalOrders} orders and ${totalRevenue.toFixed(2)} in revenue across ${channels.length} channels.</p>
<table border="1" cellpadding="6"><tr><th>Channel</th><th>Orders</th><th>Revenue</th><th>Share</th></tr>${rows}</table>`;

return channels.map(c => ({
  json: {
    week_of: weekOf, channel: c.channel, order_count: c.order_count,
    revenue: c.revenue, total_orders: totalOrders, total_revenue: totalRevenue,
    generated_at: generatedAt, email_html: emailHtml
  }
}));

After this node, the data is one item per channel. For a typical week it might look like:

[
  { "channel": "web", "order_count": 84, "revenue": 9312.50, "share_via_email": "68%" },
  { "channel": "pos", "order_count": 22, "revenue": 2140.00 },
  { "channel": "instagram", "order_count": 9, "revenue": 733.00 },
  { "channel": "shopify_draft_order", "order_count": 3, "revenue": 1180.00 }
]
💡

Tip: Orders with no channel (rare, but it happens with some app-created orders) fall into an unknown bucket instead of vanishing, so your revenue total always reconciles.

4. Append each channel to Google Sheets

Add a Google Sheets node, Operation Append. Point it at your spreadsheet and sheet, then map the columns to the fields the Code node produced: week_of, channel, order_count, revenue, generated_at. Because the Code node emits one item per channel, this node appends one row per channel automatically.

📌

Your sheet’s header row must match the mapped field names exactly, or n8n will create new columns. Set up the header once: week_of | channel | order_count | revenue | generated_at.

5. Collapse to a single item

Add a Limit node with Max Items set to 1. The Sheet append fanned across every channel; the email should be sent once. Limit keeps only the first item, which still carries the full email_html table built in step 3.

6. Email the report

Add a Gmail node, Operation Send. Set:

  • To: your own address (or a shared inbox)
  • Subject: ={{ 'Sales channel report — week of ' + $json.week_of }}
  • Message: ={{ $json.email_html }}

Save the workflow and toggle it Active. Click Execute Workflow once to confirm the email arrives and the Sheet fills. From then on it runs itself every Monday.

Common mistakes

  • Emailing before collapsing. If you wire Gmail straight after Google Sheets, it fires once per channel and you get four emails. The Limit node is what makes it a single send. Do not skip it.
  • Status left on default. If you leave Status unset, the Shopify node returns only open orders and your totals will not match Shopify Analytics. Set it to any.
  • Mismatched Sheet headers. Field names in the Google Sheets mapping must match your header row character for character, or you end up with duplicate columns and half-empty rows.
  • Building the date window by hand. Use $now.minus(7, 'days') rather than a hardcoded date, or the report silently keeps reporting the same week forever.
  • Summing a string. Shopify sends total_price as a string like "129.00". The Code node runs it through parseFloat; if you aggregate it elsewhere without parsing, you will concatenate text instead of adding numbers.

Cost at realistic volume

This workflow is effectively free to run. It fires once a week, so it uses roughly four executions a month.

Component Cost Notes
n8n (self-hosted) $0 Runs on your own server; 4 executions/month is nothing
n8n Cloud (Starter) ~$20/mo Only if you do not self-host; covers thousands of executions
Shopify Admin API $0 Reading orders is free and within rate limits
Google Sheets + Gmail $0 Standard free Google account

Even a store doing 4,000 orders a week stays cheap: Return All paginates at 250 orders per request, so that is about 16 API calls in a single Monday run, then one in-memory grouping pass. There is no per-order cost and nothing scales with your revenue.

🚀 Download the sales channel report template

The full guide above is free to follow. If you would rather skip the build, the ready-to-import template is the exact workflow from this post: import the JSON, attach your Shopify, Google Sheets, and Gmail credentials, and you are reporting in under 10 minutes. Want it done for you end to end? See our done-for-you setup service.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted

Frequently asked questions

What does source_name mean on a Shopify order?

The source_name field records where an order was placed: web for the online store, pos for in-person Shopify POS, shopify_draft_order for invoices you send manually, and channel handles like instagram or google for connected sales channels. Grouping by it shows which channel actually drove revenue.

Can I run the report daily instead of weekly?

Yes. Change the Schedule Trigger to run every day and adjust the lookback in the Shopify filter and Code node from 7 days to 1 day. A daily report is noisier for small stores, so most owners keep the weekly cadence and read a clean seven-day channel mix each Monday morning.

Does the report include Shopify POS and draft orders?

It includes every order the Shopify node returns with status set to any, so POS sales appear under pos and manually sent invoices appear under shopify_draft_order. Each distinct source_name becomes its own row, so you see the full channel mix rather than only online-store sales.

Will the workflow slow down with thousands of orders?

No. The Shopify node paginates at 250 orders per request, so even 4,000 weekly orders is about 16 API calls in one run. The grouping happens in a single Code node in memory, and the workflow fires only once a week, so it stays well inside n8n and Shopify rate limits.

Can I send the channel report to Slack or Telegram instead of email?

Yes. Replace the Gmail node with a Slack or Telegram node and pass the same email_html field, or a plain-text version, as the message. The Google Sheets log stays the same, so you keep a permanent record while the alert goes wherever your team already reads notifications.

Related guides