Skip to content
312+ businesses automated avg. 14h/week savedManual workflows cost the average team €560/week fix it in 10 daysDeployed in 5–10 business days · 30-day money-back guaranteeDental · Real Estate · Agencies · E-commerce · Covered99.97% uptime SLA · Monitored 24/7 by our ops teamA full-time ops hire costs €50K+/yr PURIST delivers more in daysn8n · Make · Claude AI · 500+ workflow templatesFree automation audit limited to 5 spots this week312+ businesses automated avg. 14h/week savedManual workflows cost the average team €560/week fix it in 10 daysDeployed in 5–10 business days · 30-day money-back guaranteeDental · Real Estate · Agencies · E-commerce · Covered99.97% uptime SLA · Monitored 24/7 by our ops teamA full-time ops hire costs €50K+/yr PURIST delivers more in daysn8n · Make · Claude AI · 500+ workflow templatesFree automation audit limited to 5 spots this week312+ businesses automated avg. 14h/week savedManual workflows cost the average team €560/week fix it in 10 daysDeployed in 5–10 business days · 30-day money-back guaranteeDental · Real Estate · Agencies · E-commerce · Covered99.97% uptime SLA · Monitored 24/7 by our ops teamA full-time ops hire costs €50K+/yr PURIST delivers more in daysn8n · Make · Claude AI · 500+ workflow templatesFree automation audit limited to 5 spots this week
PURIST
312+
Clients automated
14 h/wk
Avg time saved
99.97%
Uptime SLA
< 7 days
Deploy time
PURIST AI
Claude Opus 4.7 · n8n v1.71 · <80ms
What type of business are you running? I'll show you exactly which processes we'd automate first and your estimated ROI.
Powered by n8n + Claude Opus 4.7 Get my free automation plan →

E-commerce Operations

Expert · 32 nodes

Order-to-Cash with Failure Recovery

A 32-node pipeline coordinating payment capture, inventory, invoicing, fulfillment and customer comms, with two-stage payment retry and backorder branching.

ShopifyStripeWarehouse/3PL APIXeroKlaviyoSlackAirtable
We deploy it for you

Free download, just your email, no spam, unsubscribe anytime.

This is the workflow a growing e-commerce operation eventually needs and rarely builds correctly. A single Shopify order triggers payment capture, stock allocation, Xero invoicing with fee separation, warehouse pick-pack, and two customer emails, with automatic retry on payment failure, a dedicated backorder path, and Slack escalation carrying full context when something genuinely needs a human.

Workflow diagram

flowchart TD
  A[Shopify Order Webhook] --> B[Log Raw Order]
  B --> C[Extract Order Fields]
  C --> D[Capture Stripe Payment]
  D --> E{Payment Success?}
  E -->|No| F[Wait 5 min]
  F --> G[Retry 1]
  G --> H{Retry 1 OK?}
  H -->|No| I[Wait 30 min]
  I --> J[Retry 2]
  J --> K{Retry 2 OK?}
  K -->|No| L[Slack Escalation]
  L --> M[Stop]
  E -->|Yes| N[Merge Payment Paths]
  H -->|Yes| N
  K -->|Yes| N
  N --> O[Check Stock]
  O --> P{In Stock?}
  P -->|No| Q[Backorder ETA Email]
  Q --> R[Flag Backorder]
  P -->|Yes| S[Reserve Stock]
  S --> T[Merge Stock Path]
  R --> T
  T --> U[Create Xero Invoice]
  U --> V[Calculate Stripe Fee]
  V --> W[Post Fee Expense]
  W --> X[Request Pick Pack]
  X --> Y[Wait For Tracking]
  Y --> Z[Get Tracking Number]
  Z --> AA[Order Confirmation Email]
  AA --> AB[Shipping Confirmation Email]
  AB --> AC[Log To Ops Dashboard]

Every branch shown here (IF/Switch outcomes) exists as a real conditional in the downloadable JSON, not a simplification for this diagram.

Why native app integrations are not enough

Every tool in this stack ships its own integrations. Shopify talks to Stripe. Klaviyo talks to Shopify. Your 3PL has a Shopify app. Each of these works correctly in isolation, which is exactly why the combination fails in ways that are hard to see.

The problem is that native integrations have no shared view of the order. When a payment fails, Shopify marks the order unpaid, but nothing tells the warehouse not to ship, nothing tells Xero not to invoice, and nothing decides whether to retry. Each system does its one job correctly and the coordination between them, which is where the actual business logic lives, falls to whoever notices first.

At low volume this is manageable because a human sees every order. The failure mode arrives with growth: exceptions accumulate faster than anyone reviews them, and the business develops a quiet, permanent backlog of half-processed orders that surfaces only through customer complaints.

Two-stage payment retry and why the timing matters

Payment failures are not uniform. A genuinely declined card and a transient network error look similar at the API level but need completely different responses. Retrying immediately fails for both. Retrying too slowly loses the sale.

This workflow uses a two-stage backoff: five minutes, then thirty. The first retry catches transient issues, temporary processor outages, brief network failures, rate limiting. The thirty-minute gap catches the common real-world case of a customer who notices the decline, moves money between accounts, and would succeed on a second attempt.

Only after both retries fail does a human get paged, and the Slack escalation includes the order ID, the customer, the failure reason and both retry timestamps. This is the difference between an alert someone can act on and an alert that starts an investigation. The Escalation Runbook sticky note in the workflow documents exactly what a human should do next, so the knowledge is not trapped in one person's head.

The backorder branch, telling customers early

The In Stock? node creates the second major branch. The instinct when stock is short is to hold the order and sort it out later. In practice that means the customer finds out about the delay when the delivery does not arrive, which converts a minor inventory problem into a support ticket and a refund request.

The backorder path instead sends a Klaviyo email immediately with a real ETA, flags the order, and continues through invoicing and the rest of the pipeline. The customer knows within minutes, the order stays tracked in the same system as every other order, and the business keeps the sale far more often than it would with silence.

Note that both stock branches rejoin at Merge Stock Path before invoicing. A backordered item still gets invoiced and still gets a fee line, only the fulfillment timing differs. Splitting the branches earlier and duplicating the invoicing logic in each is the more obvious design and it doubles your maintenance surface for no benefit.

Waiting for the warehouse without polling

The Wait For Tracking Number node uses n8n's webhook-resume mode rather than polling on a timer. The execution pauses, persists its state, and resumes only when the warehouse's fulfillment webhook fires with a tracking number.

This matters at scale. A polling approach with a hundred orders in flight means a hundred repeated API calls against your 3PL every interval, most returning nothing. Webhook resume holds the execution at effectively zero cost until there is genuinely something to do.

It also makes the customer communication correct. The shipping confirmation email fires when a tracking number genuinely exists, not on a guessed delay after the pick-pack request. Customers receiving a "your order has shipped" email with no working tracking link is a small detail that erodes trust disproportionately.

Observability: the part most workflows skip

Three nodes in this workflow exist purely for observability: Log Raw Order at the start, Log Order To Ops Dashboard at the end, and the Slack escalation in between. None of them affect whether an order processes correctly, and all three are the reason you can answer questions about the system later.

The final log entry records timing data for each step the order passed through. After a few weeks that table answers questions no dashboard in Shopify, Stripe or your 3PL can: what percentage of orders hit the retry path, how long the warehouse actually takes between pick-pack request and tracking number, whether backorders cluster around particular SKUs.

For a workflow this size, that visibility is not a nice-to-have. A 32-node pipeline with branches is genuinely difficult to reason about from execution logs alone, and the operational data is what tells you which branch needs attention before it becomes a problem.

Node-by-node reference

Node Type Role
Shopify Order Webhook Webhook Fires on order creation
Log Raw Order HTTP Request Durable record before processing, enables replay
Capture Stripe Payment HTTP Request Initial payment capture attempt
Payment Success? IF Entry to the retry ladder
Wait 5 Min / Wait 30 Min Wait ×2 Two-stage backoff between retry attempts
Retry Stripe Payment 1 / 2 HTTP Request ×2 The two automatic retry attempts
Escalate Payment Failure To Slack HTTP Request Human escalation with full context, after both retries
Merge Payment Success Paths Merge Rejoins first-attempt and both retry successes
Check Stock Availability HTTP Request Queries the warehouse/3PL inventory API
In Stock? IF Splits the fulfillment and backorder paths
Send Backorder ETA Email HTTP Request Immediate customer notification with a real ETA
Reserve Stock HTTP Request Allocates inventory for the in-stock path
Create Xero Invoice HTTP Request Accounting entry for the order
Calculate Stripe Fee Line / Post Fee Set + HTTP Fee separation so the bank feed reconciles
Request Pick Pack HTTP Request Fulfillment instruction to the warehouse
Wait For Tracking Number Wait (webhook resume) Zero-cost pause until the warehouse responds
Order / Shipping Confirmation Email HTTP Request ×2 Customer comms at the correct moments
Log Order To Ops Dashboard HTTP Request Final record with per-step timing data

32 total nodes in the downloadable file, including sticky-note documentation embedded directly on the canvas.

Key logic, in code

Stripe fee calculation for the Xero expense line

// Prefer the actual fee Stripe reports over a calculated estimate.
// balance_transaction is only present once the charge has settled.
const charge = $json.charge ?? {};
const gross  = Number($json.grossAmount);

const reportedFee = charge.balance_transaction?.fee != null
  ? charge.balance_transaction.fee / 100
  : null;

const stripeFee = reportedFee ?? Number((gross * 0.029 + 0.30).toFixed(2));

return [{
  json: {
    ...$json,
    stripeFee,
    netAmount: Number((gross - stripeFee).toFixed(2)),
    feeSource: reportedFee != null ? 'stripe_reported' : 'estimated',
  }
}];

Slack escalation payload with full failure context

{
  "channel": "#ops-escalations",
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "Payment failed after 2 retries" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*Order:*\n{{ $json.orderId }}" },
        { "type": "mrkdwn", "text": "*Customer:*\n{{ $json.customerEmail }}" },
        { "type": "mrkdwn", "text": "*Amount:*\n{{ $json.grossAmount }} {{ $json.currency }}" },
        { "type": "mrkdwn", "text": "*Decline reason:*\n{{ $json.last_payment_error.message }}" },
        { "type": "mrkdwn", "text": "*Retry 1:*\n{{ $json.retry1At }}" },
        { "type": "mrkdwn", "text": "*Retry 2:*\n{{ $json.retry2At }}" }
      ]
    },
    {
      "type": "context",
      "elements": [
        { "type": "mrkdwn", "text": "Next step: retry or refund in Stripe, then replay this execution from Log Raw Order." }
      ]
    }
  ]
}

Before / after

Metric Before After this workflow
Systems coordinated per order 5–7, each independently 5–7, from one trigger with shared state
Payment failures needing a human Every one Only those surviving 2 automatic retries
Customer notified of a backorder When delivery fails to arrive Within minutes, with a real ETA
Silent exception backlog Grows with volume Zero, every exception alerts with context

Prerequisites

  • n8n v1.40+ with webhook-resume Wait nodes available
  • Shopify Admin API access and order-creation webhook permission
  • Stripe secret key with payment_intents capture permission
  • A warehouse or 3PL exposing inventory, fulfillment and a fulfillment-complete webhook
  • Xero OAuth2 credentials, Klaviyo private API key, Slack bot token, Airtable PAT

Common pitfalls

Match retry timing to your payment provider's guidance

Five and thirty minutes suit card payments. SEPA direct debit, BACS and other delayed-settlement methods need days, not minutes, and retrying too aggressively can incur per-attempt fees.

Webhook-resume executions consume a slot while waiting

On self-hosted n8n with a constrained worker pool, a hundred orders awaiting tracking numbers can exhaust concurrency. Size your workers for peak in-flight orders, not peak orders per hour.

Do not invoice before payment succeeds

The invoicing nodes sit after Merge Payment Success Paths deliberately. Inverting that order produces Xero invoices for orders that never paid, which is significantly harder to unwind than it is to avoid.

Idempotency on the warehouse call

If an execution is replayed after a partial failure, Request Pick Pack can fire twice. Pass the Shopify order ID as an idempotency key so your 3PL rejects the duplicate rather than double-shipping.

Want this deployed, configured and monitored?

The template is free. Wiring in your real credentials, tuning the logic to your business, and keeping it running when an upstream API changes is what we do.

Get my free automation plan →

Frequently asked questions

Does this work with WooCommerce instead of Shopify?

Yes, replace the trigger and the order-field extraction. The payment, inventory, accounting and comms logic downstream is platform-agnostic.

What if our 3PL has no fulfillment webhook?

Replace Wait For Tracking Number with a scheduled polling loop, accepting the extra API calls. Webhook resume is preferable where available but not mandatory.

Is 32 nodes overkill for a small store?

Below roughly 50 orders a day, yes, a simpler pattern is usually enough. This earns its complexity once exception handling starts consuming real staff time every week.

Can the retry count be changed?

Yes. The ladder is explicit rather than a loop precisely so you can add, remove or re-time stages without rewriting logic. Each stage is a Wait plus an HTTP Request plus an IF.