What Actually Works When Automating eCommerce Workflows

What Actually Works When Automating eCommerce Workflows

1. Shopify order triggers behave differently across platforms

The weirdest part of rebuilding my product fulfillment Zap for the third time last month was realizing that Shopify webhook triggers don’t behave consistently across automation tools. Zapier, Make, n8n — I tried all of them, and each one had a different interpretation of the Shopify API’s order payload.

Here’s what caught me: Zapier doesn’t surface the note_attributes field from Shopify unless the order includes it and an internal parsing rule allows it (which is buried two layers deep in the webhook preview). On Make, the module just throws everything into the bundle, including metafields, tags, partial payments, the whole mess. n8n is even more raw — the webhook step just dumps full JSON, but it’s not pre-structured, so you’re stuck querying it manually using expressions like {{$json["line_items"][0].title}}.

So the same Shopify order will give you totally different maps depending on where it lands. Watching an identical order trigger three different webhook formats side-by-side was bizarre. Felt like watching parallel universes load the same webpage with different CSS.

Undocumented edge case: Shopify sometimes posts blank shipping_address objects on digital orders, even if shipping isn’t required. Zapier interprets that as null; Make treats it as an object with empty strings.

I lost an hour trying to match fields across platforms when filtering for express checkout orders. Zapier kept skipping them because I was testing with a Shopify checkout that skipped address input, which blanked out the trigger filter. No alert, just skipped silently. Very cool.

2. Airtable automations quietly ignore grouped-record updates

If you’re triggering flows based on Airtable record updates from eCommerce order syncing — like pulling inventory in from ShipBob or syncing tags from customer service tickets — you need to know this: bulk updates inside grouped views do not consistently trigger Airtable automations. I found out the hard way after grouping by product type and bulk-tagging 12 rows with “Restocking”… then waiting. Nothing happened. Zapier listened. Make didn’t. Airtable’s own automations didn’t either.

This matters when your alerts, restock projections, or internal escalations rely on catching those field changes. Apparently, bulk field edits from grouped records use a slightly different backend mutator in Airtable, and even though the UI says “12 records updated”, the automation logs nothing at all. I opened dev tools and watched the patch payload — it sends a batch object, not individual record events.

To push updates reliably from Airtable automation triggers:

  • Use single-record edits or scripts — not UI-level batch edits in groups.
  • Fill a checkbox to trigger a run, instead of relying on data diff conditions.
  • Use Make instead of Airtable native automations — it catches batch edits through its polling connector more accurately.
  • For Zapier, add a timestamp field for a manual log — that reruns the automation regardless.
  • If possible, structure your flow to avoid depending on updates at all — write new rows instead.

This one burned me during a mid-month surge. We added 250 SKU units, updated the incoming inventory table via CSV import — and all three automations supposed to route tickets by product went dead. I had to manually re-sync order rows at 2AM by changing a hidden notes field just to trigger the damn automations.

3. Looping through line items varies wildly by platform

Here’s the dumb trap I fell in twice in the same week: I had an order object from Shopify with line items — so naturally I looped them using the native looping features inside Zapier and Make. But they do not handle these the same:

  • Zapier’s “Looping by Zapier” splits at the top level, but assumes all iterations are base-type values (like strings or numbers)
  • Make cycles through native array objects and passes their structure — but your modules downstream must reference each subfield explicitly in the loop context
  • n8n requires a Function node before and after the loop to structure the payload entries correctly — it doesn’t natively extract nested arrays

The real trap: Zapier silently flattens nested arrays inside object arrays. If I had 3 line items with options, the loop step stored the options field as a comma-separated string — not as structured JSON. So any step checking for color = “green” just failed, because the value was "size: medium, color: green" — not a parseable object.

I found this when orders with multiple variants weren’t triggering fulfillment steps right. Pulled the Task History and saw fulfillment failed on step 6. Turns out the looped iterator had bundled SKU data into a flattened value that our warehouse couldn’t parse.

The fix? Use Code by Zapier to manually loop the array and pass structured outputs via line-by-line mapping. It’s a hassle, but it preserves the subkeys — especially for stores with bundling or product customizations.

4. Make webhooks re-fire on duplicate ID payloads under load

This one scared me. During a high volume day, our Make scenario started firing duplicate runs off the same Shopify order ID. The webhook was set to catch new paid orders. But 2–3 runs per order started spawning every few minutes, triggering unexpected email confirmations and inventory updates. Felt like we were getting looped or DOS’ed by our own automation.

The logs showed that Shopify sometimes re-sends webhook payloads with the same order ID under network conditions where the original failed to deliver a 200. Fair enough. But Make doesn’t consistently deduplicate these events, especially if the payload comes in outside the original execution window. There is no built-in webhook deduplication toggle — just silence or chaos.

I filed a ticket. Support replied: “Make scenarios triggered by custom webhooks are executed whenever the endpoint receives data. Deduplication must be handled within the structure of the scenario.” Cool.

A quick fix was adding a Datastore module that tracked last-triggered order IDs. Not elegant, but it worked. The logic looked like:

If incoming.order_id exists in Datastore
  Then stop scenario
  Else: run → store order_id

In hindsight I probably should have handled that in Shopify config with retry ceilings, but we’d never seen three retries in five minutes before. It happened during a WiFi hiccup while I was testing something else on the same subnet. Dumb but real.

5. Customer service tagging skips ticket threads in HelpScout

I remember thinking it’d be simple: auto-tag HelpScout tickets when customer messages include product keywords. Set the trigger → scan recent message → tag if keyword found. Should’ve worked. But one out of every five tickets skipped tagging. No error. No warning. Just… skipped.

The problem came down to how HelpScout’s thread object loads the conversations in the API. When a user replies from mobile, sometimes the message lands as a hidden note or a restricted type. Zapier and Make see them, but only if you explicitly parse the threads array with sub-filters for message-type == customer.

I fixed it by adding a JavaScript filter step before tagging, pulling the last n threads where type === "customer" and status === "active". HelpScout doesn’t expose this in their Zapier app though — you need webhooks or API modules to get thread fidelity.

A thread from a user who replied “Still waiting on the order from last week” never got tagged because the reply came via iMessage relay and the system treated it as an unsupported source. I ended up manually replying with no tag, which threw off our SLA report.

6. Notion databases misbehave under concurrent order logging

If you’re tracking orders, returns, or support touchpoints in Notion databases via automation, prepare to slow down or mess up writes if concurrency spikes. Our store had back-to-back influxes from two product launches, and both flows hit Notion as part of a checkout audit system. Towards the peak, Notion started erroring writes out — but only for page writes with multiple property types in the payload.

If you pass 4+ property types in a single Create or Update in Notion using Make or Zapier, and if more than one actor (even virtual) touches that database at once — your job will randomly fail with a 429 or 502, but the timeout won’t get caught. Make just logs it as a “Succeeded” step with null content. Zapier retries incorrectly. And your dashboard ends up missing rows you think are there.

The workaround? Queue writes into a schedule. I now dump Notion updates into a temporary DB and use a delayed release job to submit them at 2-second intervals. Ugly, but stable. Tried the fancy route of batching updates using a Notion integration — it hit the same cap and died silently.

What nailed it down for me was this little debug quote in a backscroll of Make scenario logs:

“action_limits.blocked_by_rate_limiter” — response returned null on success

So it thought it worked. But it didn’t. And no one said anything. That run failed silently for 14 orders.

7. Splitting discount logic between checkout and fulfillment steps fails quietly

We had two promo codes running — one percent-based, one fixed dollar discount. Checkout worked fine. I passed discount info into the backend to record deal source in customer notes. Fulfillment was supposed to tag orders based on discount type… but nothing happened. Again. No errors. No crashes. Just nothing done.

The Shopify payload gives discount data in discount_applications and discount_codes arrays. But here’s the kicker: discount_applications often loads after the webhook is sent — especially if multiple codes are stacked or multiple sources apply.

The fulfillment system was using discount_codes[0].code, expecting it to always match content in discount_applications[0]. Didn’t happen. For multiple stacked discounts, those arrays misalign or only partially populate. The same order went out with the wrong promo tag to our shipping team. Twice.

Eventually I dumped a webhook payload from Shopify in real time and saw:

"discount_codes": [“BLACKFRIDAY”],
"discount_applications": []

because the second application was delayed due to payment processing. But Zapier sent the Webhook before it finalized. My fix? Add a delay + re-fetch step. Use a GET to Shopify Orders API after 10 seconds to ensure the discount arrays have populated fully. That one extra call made all the difference.