Intermediate~15 min setupCRM & E-commerceVerified April 2026
HubSpot logo
Shopify logo

How to Automate HubSpot Lifecycle Stages from Shopify with Pipedream

Automatically updates a HubSpot contact's lifecycle stage when a Shopify customer makes their first purchase, places a repeat order, or crosses a total spend threshold.

Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.

Best for

E-commerce marketing teams who need HubSpot lifecycle stages to reflect real Shopify purchase behavior without manual CSV imports or guesswork.

Not ideal for

Stores with high order volumes needing bulk backfills — use a direct HubSpot API script or Fivetran for historical data instead.

Sync type

real-time

Use case type

sync

Real-World Example

💡

A 12-person DTC apparel brand uses this to move customers from 'Lead' to 'Customer' the moment their first Shopify order is paid, then to 'Evangelist' once they hit $500 in total spend. Before this workflow, the marketing team ran a manual HubSpot import every Friday — lifecycle stages were always 4-7 days stale, meaning new buyers were still receiving acquisition emails days after purchasing.

What Will This Cost?

Drag the slider to your expected monthly volume.

/mo
505005K50K

Each platform counts differently — Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.

Prices shown for annual billing. Based on published pricing as of April 2026.

Estimated ROI

1000

min saved/mo

$583

labor value/mo

Free

no platform cost

Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.

Implementation

Skip the setup

Import this workflow directly into Pipedream

Copy the pre-built Pipedream blueprint and paste it straight into Pipedream. All modules, filters, and field mappings are already configured — you just need to connect your accounts.

Before You Start

Make sure you have everything ready.

Shopify Private App with read_orders and read_customers API scopes enabled
HubSpot Private App token with crm.objects.contacts.read and crm.objects.contacts.write scopes
Custom HubSpot contact properties created for shopify_total_spend (number) and shopify_order_count (number)
Pipedream account — free tier supports up to 10,000 events/month before credits are consumed

Optional

Slack webhook URL or Slack app connection in Pipedream for error alerting (optional but recommended for production)

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Customer Emailemail
Lifecycle Stagelifecyclestage
Total Shopify Spendshopify_total_spend
Shopify Order Countshopify_order_count
4 optional fields▸ show
First Namefirstname
Last Namelastname
Last Order Dateshopify_last_order_date
Shopify Customer IDshopify_customer_id

Step-by-Step Setup

1

pipedream.com > Workflows > + New Workflow

Create a new Pipedream workflow

Go to pipedream.com and log in. From the left sidebar, click 'Workflows', then click the blue '+ New Workflow' button in the top right. Give the workflow a clear name like 'Shopify → HubSpot Lifecycle Stages'. You'll land on the workflow canvas with an empty trigger slot waiting.

  1. 1Click 'Workflows' in the left sidebar
  2. 2Click '+ New Workflow' in the top right
  3. 3Type a name: 'Shopify → HubSpot Lifecycle Stages'
  4. 4Click 'Create'
What you should see: You should see a blank workflow canvas with a grey trigger block at the top labeled 'Add a trigger'.
2

Workflow Canvas > Add a trigger > Shopify > New Order (Instant)

Add the Shopify order paid webhook trigger

Click the grey 'Add a trigger' block. In the search box, type 'Shopify' and select it. From the event list, choose 'New Order (Instant)' — this fires via webhook the moment Shopify marks an order as paid. Connect your Shopify store by clicking 'Connect Shopify Account' and entering your store URL and API credentials. Pipedream will auto-register the webhook in your Shopify admin.

  1. 1Click the 'Add a trigger' block
  2. 2Search for 'Shopify' in the app search bar
  3. 3Select 'New Order (Instant)' from the trigger list
  4. 4Click 'Connect Shopify Account'
  5. 5Enter your Shopify store URL (e.g., yourstore.myshopify.com) and generate a Private App API key
  6. 6Click 'Save and continue'
What you should see: You should see a green 'Connected' badge next to your Shopify account and a 'Test trigger' button. Clicking it will load a sample order payload from your store.
Common mistake — Pipedream creates the webhook in Shopify automatically, but if you already have a conflicting webhook at the same endpoint from a previous workflow, Shopify will fire duplicate events. Check Shopify Admin > Settings > Notifications > Webhooks before saving.
Pipedream
+
click +
search apps
HubSpot
HU
HubSpot
Add the Shopify order paid w…
HubSpot
HU
module added
3

Workflow Canvas > + Add a step > Run Node.js code

Extract and normalize order data in a Node.js code step

Click '+ Add a step' below the trigger and choose 'Run Node.js code'. This step pulls the customer email, order total, and order count from the Shopify payload. You'll also set a preliminary lifecycle stage variable here based on the order data — this keeps the logic in one place before you make any API calls. Paste the code from the Pro Tip section below into this code step.

  1. 1Click '+ Add a step'
  2. 2Select 'Run Node.js code'
  3. 3Paste the lifecycle classification code into the editor
  4. 4Click 'Test' to verify the step runs against the sample Shopify payload
What you should see: The test output panel should show a JSON object with fields: customer_email, total_spent, order_count, and lifecycle_stage (one of 'customer', 'opportunity', or 'evangelist').
Common mistake — Shopify's order payload uses total_price (a string like '149.99') not a number. The code step must parse it with parseFloat() before doing any comparisons — skipping this will cause threshold checks to silently fail.

Paste this into the Node.js code step (step 3) that sits immediately after the Shopify trigger. It parses the raw Shopify order payload, calculates lifecycle stage based on order count and total spend thresholds, and exports clean values for every downstream HubSpot step to reference via steps.classify_lifecycle.

JavaScript — Code Step// Step name: classify_lifecycle
▸ Show code
// Step name: classify_lifecycle
// Pipedream Node.js code step — paste into 'Run Node.js code' after Shopify trigger
export default defineComponent({

... expand to see full code

// Step name: classify_lifecycle
// Pipedream Node.js code step — paste into 'Run Node.js code' after Shopify trigger

export default defineComponent({
  async run({ steps, $ }) {
    const order = steps.trigger.event.body;

    // Safely extract customer data from Shopify payload
    const customer = order.customer;
    if (!customer || !customer.email) {
      return $.flow.exit('No customer email in order payload — skipping.');
    }

    const email = customer.email.trim().toLowerCase();
    const firstName = customer.first_name || '';
    const lastName = customer.last_name || '';
    const orderCount = parseInt(customer.orders_count, 10) || 1;
    
    // Shopify sends total_spent as a string — parse it
    const totalSpent = parseFloat(customer.total_spent) || 0;

    // Define your lifecycle thresholds here
    // Adjust SPEND_THRESHOLD and REPEAT_ORDER_MIN to match your store
    const SPEND_THRESHOLD_EVANGELIST = 1000; // $1,000+ total spend
    const SPEND_THRESHOLD_OPPORTUNITY = 400; // $400+ total spend
    const REPEAT_ORDER_MIN = 2;              // 2+ orders = repeat customer

    // Determine lifecycle stage based on purchase behavior
    let lifecycleStage;
    if (totalSpent >= SPEND_THRESHOLD_EVANGELIST) {
      lifecycleStage = 'evangelist';
    } else if (totalSpent >= SPEND_THRESHOLD_OPPORTUNITY || orderCount >= REPEAT_ORDER_MIN) {
      lifecycleStage = 'opportunity';
    } else if (orderCount === 1) {
      lifecycleStage = 'customer';
    } else {
      lifecycleStage = 'lead';
    }

    const orderDate = new Date(order.created_at).toISOString().split('T')[0];

    console.log(`Customer: ${email} | Orders: ${orderCount} | Spent: $${totalSpent} | Stage: ${lifecycleStage}`);

    // Return values for downstream steps
    return {
      customer_email: email,
      first_name: firstName,
      last_name: lastName,
      total_spent: totalSpent,
      order_count: orderCount,
      lifecycle_stage: lifecycleStage,
      shopify_customer_id: String(customer.id),
      last_order_date: orderDate,
    };
  },
});
4

Workflow Canvas > + Add a step > HubSpot > Search Contacts

Look up the HubSpot contact by email

Add another step and select the HubSpot app. Choose the action 'Search Contacts'. Set the search property to 'email' and the value to the customer email extracted in the previous step using the reference {{steps.nodejs_1.customer_email}}. Connect your HubSpot account via 'Connected Accounts' — you'll need a Private App token with contacts read/write scope. This step returns the HubSpot contact ID you need for the update call.

  1. 1Click '+ Add a step'
  2. 2Search for 'HubSpot' and select it
  3. 3Choose the action 'Search Contacts'
  4. 4Set 'Property' to 'email'
  5. 5Set 'Value' to {{steps.nodejs_1.customer_email}}
  6. 6Click 'Connect HubSpot Account' and paste your Private App token
What you should see: The test output should show a HubSpot contact object with an 'id' field (a numeric string like '12345678') and current property values including lifecyclestage.
Common mistake — HubSpot's contact search returns an array even when there's one match. Reference the contact ID as steps.search_contacts.$return_value.results[0].id — not steps.search_contacts.$return_value.id or you'll get undefined.
5

Workflow Canvas > + Add a step > Run Node.js code

Handle the case where no HubSpot contact exists

Add a Node.js code step immediately after the search step. Check whether the results array is empty. If no contact is found, use the HubSpot API to create a new contact with the customer's email, first name, and last name from the Shopify payload. Return the new contact's ID so subsequent steps can reference it consistently. This prevents the update step from throwing a 404 on net-new Shopify customers.

  1. 1Click '+ Add a step'
  2. 2Select 'Run Node.js code'
  3. 3Write conditional logic: if results array length is 0, call HubSpot POST /crm/v3/objects/contacts
  4. 4Return the contact ID whether created or found
  5. 5Click 'Test' to verify both branches work
What you should see: The step output should always return a single contact_id string regardless of whether the contact was pre-existing or newly created.
6

Workflow Canvas > + Add a step > Run Node.js code

Add a filter step to prevent lifecycle stage downgrades

HubSpot's lifecycle stage field has a strict progression: you cannot move a contact backward (e.g., from 'Customer' back to 'Lead') via a standard property update — but you can via API if you force it, which corrupts your funnel data. Add a Node.js code step that compares the contact's current lifecyclestage from the search result against the new stage you're about to set. If the new stage is not an advancement, export a flag and use Pipedream's $.flow.exit() to stop the workflow cleanly.

  1. 1Click '+ Add a step'
  2. 2Select 'Run Node.js code'
  3. 3Define the stage order array: ['subscriber', 'lead', 'marketingqualifiedlead', 'salesqualifiedlead', 'opportunity', 'customer', 'evangelist']
  4. 4Compare current stage index to new stage index
  5. 5Call $.flow.exit('Stage not an advancement') if the new stage index is not higher
What you should see: When you test with a contact already marked 'customer' and a new stage of 'customer', the step should show 'Workflow exited early' in the run log — not an error.
Common mistake — $.flow.exit() stops the workflow silently with a success status in Pipedream's UI. This is intentional — do not mistake it for an error. Check your run logs if you expect more updates than you're seeing.
HubSpot
HU
trigger
filter
Condition
matches criteria?
yes — passes through
no — skipped
Shopify
SH
notified
7

Workflow Canvas > + Add a step > HubSpot > Update Contact

Update the HubSpot contact's lifecycle stage

Add a HubSpot step and choose 'Update Contact'. Set 'Contact ID' to the resolved contact_id from step 5. Map 'Lifecycle Stage' to the lifecycle_stage value from step 3's code output. You can also map additional fields here: total Shopify spend to a custom HubSpot property, and order count to another. This is the step that actually writes to HubSpot.

  1. 1Click '+ Add a step'
  2. 2Search for 'HubSpot' and select 'Update Contact'
  3. 3Set 'Contact ID' to {{steps.resolve_contact.contact_id}}
  4. 4Set 'Lifecycle Stage' to {{steps.nodejs_1.lifecycle_stage}}
  5. 5Add custom property: shopify_total_spend = {{steps.nodejs_1.total_spent}}
  6. 6Add custom property: shopify_order_count = {{steps.nodejs_1.order_count}}
  7. 7Click 'Test'
What you should see: HubSpot should return a 200 response with the updated contact object showing the new lifecyclestage value. Verify in HubSpot CRM > Contacts > [contact name] that the stage has changed.
Common mistake — HubSpot lifecycle stage API values are all lowercase with no spaces: 'marketingqualifiedlead', 'salesqualifiedlead', 'customer', 'evangelist'. Sending 'Customer' or 'Marketing Qualified Lead' will return a 400 error.
8

Workflow Canvas > + Add a step > Run Node.js code OR Settings > Error Handling

Add error handling and Slack alert for failures

Add a final Node.js code step that wraps in a try/catch. If any upstream step has thrown an error (check steps via Pipedream's event inspection), post a message to a Slack channel with the customer email, the attempted lifecycle stage, and the error message. This gives your team visibility without requiring manual log checks. Alternatively, use Pipedream's built-in error workflow feature under Settings > Error Handling.

  1. 1Click '+ Add a step'
  2. 2Select 'Run Node.js code'
  3. 3Add a try/catch block referencing upstream step outputs
  4. 4On catch, use the Slack API or Pipedream's Slack app to post to #ecommerce-alerts
  5. 5Alternatively, go to workflow Settings > Error Handling and set a dedicated error workflow
What you should see: Force a test error by passing an invalid contact ID in step 7 — you should receive a Slack message within 30 seconds containing the email and error details.
9

Workflow Canvas > Deploy > Events tab

Deploy and verify with a live test order

Click 'Deploy' in the top right of the Pipedream workflow canvas. Place a real test order in your Shopify store (use a $0.00 discount code if needed) with an email tied to an existing HubSpot test contact. Watch the Pipedream run log in real time under the 'Events' tab. Confirm the contact's lifecycle stage updates in HubSpot within 60 seconds of the order being marked paid.

  1. 1Click 'Deploy' button in the top right
  2. 2Place a test order in Shopify with a known contact email
  3. 3Open Pipedream > Workflows > [your workflow] > Events tab
  4. 4Watch for the new event to appear and click it to inspect each step's output
  5. 5Verify in HubSpot CRM that the contact's lifecycle stage has updated
What you should see: You should see a green checkmark on every step in the Events tab, and the HubSpot contact record should show the updated lifecycle stage within 60 seconds of the Shopify order being paid.
Common mistake — Shopify only sends the 'orders/paid' webhook when an order transitions to paid status. If you create a test order with a manual payment method set to 'pending', the webhook will not fire. Use 'Bogus Gateway' in Shopify's test mode to simulate a completed payment.
Pipedream
▶ Deploy & test
executed
HubSpot
Shopify
Shopify
🔔 notification
received

Going live

Production Checklist

Before you turn this on for real, confirm each item.

Troubleshooting

Common errors and how to fix them.

Frequently Asked Questions

Common questions about this workflow.

Analysis

VerdictWhy n8n for this workflow

Use Pipedream for this if your team includes at least one developer or a technical marketer comfortable with JavaScript. The Node.js code step is where this workflow earns its complexity — you need conditional logic across three threshold checks, a downgrade guard, and a create-or-update pattern for HubSpot contacts. Pipedream handles all of that in a single workflow without duct-taping together multiple tools. The one scenario where you'd pick something else: if your team is entirely non-technical, Make's visual conditional router handles the same branching logic without writing a line of code.

Cost

Pipedream pricing works on a credit model. Each workflow run consumes credits based on compute time. A typical run of this workflow — trigger + 4 steps + one HubSpot API call — costs roughly 10-15 credits. At 1,000 Shopify orders/month, that's 10,000-15,000 credits. Pipedream's free tier includes 10,000 credits/month, so you'll hit the ceiling right around 1,000 orders. The Basic plan at $19/month gives you 100,000 credits — enough for roughly 7,000-10,000 orders/month. Make's equivalent workflow costs about $9/month at the same volume using their operations model. Pipedream costs more at scale, but the code flexibility offsets it if you're doing anything non-trivial.

Tradeoffs

Zapier has a pre-built HubSpot 'Update Contact' action that's genuinely easier to configure for simple field mapping — no code required — but it can't handle the create-or-update pattern without a second Zap and a Filter step, which burns through tasks fast. Make's router module handles the multi-branch lifecycle logic visually and costs less, but debugging complex data transformations in Make's formula editor is painful compared to reading a Node.js stack trace. n8n's Function node is nearly identical to Pipedream's code step but requires self-hosting or n8n Cloud — add operational overhead. Power Automate has no native Shopify trigger; you'd need a third-party connector. Pipedream wins here because the webhook is instant, the code step is first-class, and the Shopify + HubSpot integrations are maintained by the Pipedream team.

Three things you'll hit after setup. First: HubSpot's lifecycle stage field does not fire a property change webhook when updated via API the same way it does via the UI — if you're planning to use HubSpot Workflows triggered by lifecycle stage changes, test this explicitly before assuming the chain works. Second: Shopify's orders_count on the customer object reflects all orders, including cancelled and refunded ones. A customer who ordered twice and refunded once shows orders_count: 2, which may incorrectly qualify them as a repeat buyer. Add a check against order.financial_status to filter out refunded orders. Third: Pipedream has a 30-second execution timeout per step by default. If you batch any HubSpot calls or add retries, stay well under that limit or your step will time out mid-write and leave the contact in a partial update state.

Ideas for what to build next

  • Trigger HubSpot Workflows from Lifecycle Stage ChangesNow that lifecycle stages update in real time, configure HubSpot Workflow automations to enroll contacts in email sequences when they hit 'customer' or 'evangelist' — this closes the loop between Shopify purchase data and your marketing campaigns.
  • Add a Reverse Sync for HubSpot-Qualified Leads to ShopifyBuild a second Pipedream workflow that listens for HubSpot lifecycle stage changes (via HubSpot webhook) and adds a Shopify customer tag like 'hs-evangelist' — this lets your Shopify store display loyalty pricing or exclusive products to high-value HubSpot segments.
  • Enrich with Refund and Churn SignalsExtend this workflow to also listen for Shopify 'refunds/create' webhooks and move contacts back to an appropriate stage or add a HubSpot contact property like shopify_refund_count — giving your sales team visibility into at-risk customers before they churn.

Related guides

Was this guide helpful?
HubSpot + Shopify overviewPipedream profile →