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

How to Sync Shopify Orders to HubSpot for Revenue Attribution with Power Automate

When a Shopify order is placed, Power Automate writes the order value, campaign source, and UTM data back to the matching HubSpot contact so your marketing team can see exactly which campaigns generated revenue.

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

Best for

Marketing teams already inside the Microsoft 365 ecosystem who need Shopify order revenue reflected on HubSpot contact records for campaign ROI reporting.

Not ideal for

Teams that need sub-minute attribution latency or that store UTM data exclusively in a custom Shopify storefront not passing note_attributes — use Make or n8n instead.

Sync type

real-time

Use case type

reporting

Real-World Example

💡

A 12-person DTC apparel brand runs paid campaigns across Google, Meta, and email. Orders come into Shopify but the marketing manager had no way to tie a $340 order back to the Google Ads campaign that drove it — HubSpot showed contacts but no revenue. After this flow, every Shopify order fires a webhook that writes order value, source, and UTM campaign name directly onto the HubSpot contact. The team now filters HubSpot contacts by campaign source and sees total attributed revenue per channel in under 10 seconds.

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 Power Automate

Copy the pre-built Power Automate blueprint and paste it straight into Power Automate. 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 Custom App with read_orders and read_customers Admin API scopes enabled
HubSpot account with Super Admin or Marketing Access role so OAuth grants write access to contact properties
Custom HubSpot contact properties pre-created: last_order_revenue (Number), last_utm_source (Single-line text), last_utm_medium (Single-line text), last_utm_campaign (Single-line text), last_order_date (Date)
Shopify storefront configured with a UTM capture script that writes utm_source, utm_medium, and utm_campaign into Shopify order note_attributes at checkout
Power Automate license that includes premium connectors — the HubSpot CRM (V3) connector is a premium connector and requires a Power Automate Per User or Per Flow plan

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Contact Emailemail
Last Order Revenuelast_order_revenue
Last Order Datelast_order_date
UTM Sourcelast_utm_source
UTM Campaignlast_utm_campaign
4 optional fields▸ show
UTM Mediumlast_utm_medium
Shopify Order IDlast_shopify_order_id
Order Currencylast_order_currency
Total Orders Counttotal_shopify_orders

Step-by-Step Setup

1

make.powerautomate.com > My flows > + New flow > Automated cloud flow

Create a new Automated cloud flow in Power Automate

Go to make.powerautomate.com and sign in. In the left sidebar click 'My flows', then click '+ New flow' at the top and select 'Automated cloud flow'. Give it a descriptive name like 'Shopify Order → HubSpot Revenue Attribution'. You will be prompted to choose a trigger — search for 'Shopify' in the trigger picker and select 'When an order is created'. This sets up the flow to fire the instant Shopify fires its order/paid webhook.

  1. 1Click 'My flows' in the left sidebar
  2. 2Click '+ New flow' then select 'Automated cloud flow'
  3. 3Type a name: 'Shopify Order → HubSpot Revenue Attribution'
  4. 4In the trigger search box type 'Shopify'
  5. 5Select 'When an order is created' from the Shopify connector
What you should see: You should see the Shopify trigger card appear as the first block in your flow canvas, showing 'When an order is created' with a connection prompt.
Common mistake — The Shopify connector in Power Automate uses a polling mechanism underneath even though it surfaces as an event trigger. Orders typically appear within 2–5 minutes, not instantly. If your reporting needs sub-minute attribution, this connector alone won't meet that bar.
2

Shopify Admin > Apps > Develop apps > Create app > API credentials

Authenticate the Shopify connection

Click 'Sign in' inside the trigger card. Power Automate will ask for your Shopify store URL (format: yourstore.myshopify.com) and an API access token. Generate a Custom App in Shopify Admin with read_orders scope, then paste that Admin API access token here. The connection will be saved under Connections in the left sidebar and reused across all steps.

  1. 1In Shopify Admin, go to Settings > Apps and sales channels > Develop apps
  2. 2Click 'Create an app', name it 'Power Automate Attribution'
  3. 3Under 'Configuration', enable the 'read_orders' and 'read_customers' Admin API scopes
  4. 4Click 'Install app' then copy the Admin API access token
  5. 5Paste the store URL and token into the Power Automate connection prompt and click 'Create'
What you should see: The Shopify trigger card shows your store name in a dropdown and the connection displays a green checkmark under Data > Connections in the left sidebar.
Common mistake — The Admin API access token is shown only once in Shopify. Copy it immediately. If you miss it, you must rotate the token and update the Power Automate connection manually.
3

Flow canvas > + New step > Data Operation > Parse JSON

Add a Parse JSON action to extract UTM and order fields

Click '+ New step' below the trigger. Search for 'Parse JSON' and select the built-in Data Operation action. In the Content field, insert the dynamic value 'Body' from the Shopify trigger. Click 'Generate from sample' and paste in a real Shopify order JSON payload — specifically you need the note_attributes array where Shopify stores UTM parameters passed via the storefront. This step turns the raw webhook body into named, usable fields for downstream actions.

  1. 1Click '+ New step'
  2. 2Search 'Parse JSON' and select it under Data Operation
  3. 3In the Content field, click the lightning bolt and select 'Body' from the Shopify trigger
  4. 4Click 'Generate from sample' and paste a sample Shopify order JSON
  5. 5Confirm the schema shows fields: id, email, total_price, note_attributes, created_at
What you should see: The Parse JSON card shows a green schema preview. Dynamic content from this step now includes fields like 'email', 'total_price', 'note_attributes', and 'created_at' in the dynamic value picker.
Common mistake — UTM parameters in Shopify are only available in note_attributes if your storefront is configured to capture them via landing page scripts (e.g., a UTM cookie script). If note_attributes is empty in your sample payload, UTM data is not being captured and you need to fix the storefront before this flow will produce useful attribution data.
HubSpot fields
firstname
lastname
email
company
hs_lead_status
available as variables:
1.props.firstname
1.props.lastname
1.props.email
1.props.company
1.props.hs_lead_status
4

Flow canvas > + New step > Control > Apply to each

Apply for each loop to extract UTM values from note_attributes

The note_attributes field is an array of name/value pairs like [{"name":"utm_source","value":"google"},{"name":"utm_campaign","value":"spring-sale"}]. Add an 'Apply to each' control action, setting the input to the note_attributes array from your Parse JSON step. Inside the loop, add a 'Condition' to check if the current item's name equals 'utm_source', 'utm_medium', or 'utm_campaign'. Use 'Set variable' actions to capture each value into flow-level variables you initialize before this loop.

  1. 1Click '+ New step', select Control > Apply to each
  2. 2In the 'Select an output from previous steps' field, select 'note_attributes' from Parse JSON
  3. 3Inside the loop, add Control > Condition
  4. 4Set the condition: items('Apply_to_each')?['name'] equals 'utm_source'
  5. 5In the Yes branch, add Data Operation > Set variable and store items('Apply_to_each')?['value'] into a pre-initialized variable called varUTMSource
What you should see: Your flow canvas shows an Apply to each loop with nested conditions. Each condition branch populates a separate variable (varUTMSource, varUTMMedium, varUTMCampaign) when the matching note_attribute name is found.
5

Flow canvas > + New step > Variables > Initialize variable

Initialize variables before the loop

Before the Apply to each loop, add three 'Initialize variable' actions — one for each UTM field. Set all three to Type: String and Value: empty string. Power Automate requires variables to be initialized at the top level of the flow, not inside a loop or condition branch. Name them varUTMSource, varUTMMedium, and varUTMCampaign. Add a fourth variable varOrderTotal of Type: Float to hold the Shopify order total_price value, set it immediately to the total_price from Parse JSON.

  1. 1Click '+ New step' directly under Parse JSON (before the loop)
  2. 2Search 'Initialize variable' and add it
  3. 3Set Name: varUTMSource, Type: String, Value: (empty)
  4. 4Repeat for varUTMMedium and varUTMCampaign
  5. 5Add a fourth Initialize variable: varOrderTotal, Type: Float, Value: select total_price from Parse JSON dynamic content
What you should see: Four Initialize variable blocks sit between Parse JSON and the Apply to each loop. Each shows its name, type, and default value in the card summary.
Common mistake — If you place Initialize variable inside a condition or loop, Power Automate will throw a runtime error: 'Variables cannot be initialized within loops or conditions'. They must live at the top-level flow scope.
6

Flow canvas > + New step > HubSpot CRM (V3) > Get contact by email

Search for the matching HubSpot contact by email

After the loop, add a new step using the HubSpot CRM (V3) connector. Select the action 'Search contacts by email' or use 'Get contact by property'. Pass in the email field from your Parse JSON output as the search value. This returns the HubSpot internal contact ID (hs_object_id) you need to update the right record. If no match is found, you will handle that in the next step with a condition.

  1. 1Click '+ New step' after the Apply to each loop
  2. 2Search 'HubSpot' and select the HubSpot CRM (V3) connector
  3. 3Choose 'Get contact by email address' action
  4. 4In the Email field, select 'email' from Parse JSON dynamic content
  5. 5Leave additional properties blank for now
What you should see: The HubSpot action card shows the email field populated with the dynamic Shopify email value. When you test it, the action returns a JSON object containing the contact's 'id' (hs_object_id) and current property values.
Common mistake — HubSpot returns a 404 if no contact matches the email. Your flow will fail here unless you add error handling. Set 'Configure run after' on the next step to run on both success and failure, or add a Condition checking if the HubSpot response body is empty before proceeding.
7

Flow canvas > + New step > Control > Condition

Add a condition to handle unmatched contacts

Add a Control > Condition step. Check whether the HubSpot Get contact action body contains a valid 'id' field — use the expression: empty(body('Get_contact_by_email_address')?['id']). In the 'Yes' branch (no contact found), add a HubSpot 'Create contact' action using the Shopify email, and optionally the customer first and last name from the Shopify order. In the 'No' branch (contact found), proceed to the update step. This prevents the flow from failing on first-time buyers.

  1. 1Click '+ New step', choose Control > Condition
  2. 2In the left field, paste expression: empty(body('Get_contact_by_email_address')?['id'])
  3. 3Set operator to 'is equal to' and right value to 'true'
  4. 4In the Yes branch, add HubSpot CRM (V3) > Create contact with email from Parse JSON
  5. 5In the No branch, leave empty for now — this is where the update action goes in Step 8
What you should see: The condition card splits into Yes and No branches. The Yes branch shows a HubSpot Create contact action. The No branch is ready for the update action in the next step.
Common mistake — Filters are the most common place setups break. Double-check the field name and value exactly match what your app sends — a single capital letter difference will block everything.
HubSpot
HU
trigger
filter
Condition
matches criteria?
yes — passes through
no — skipped
Shopify
SH
notified
8

Flow canvas > Condition No branch > + Add an action > HubSpot CRM (V3) > Update contact

Update the HubSpot contact with Shopify revenue and attribution fields

Inside the No branch of your condition (and after the Create contact in the Yes branch), add a HubSpot CRM (V3) 'Update contact' action. In the Contact ID field, use the expression: body('Get_contact_by_email_address')?['id']. Map the custom HubSpot properties you've pre-created for attribution: last_order_revenue, last_utm_source, last_utm_medium, last_utm_campaign, and last_order_date. Use your flow variables (varUTMSource, etc.) and the Parse JSON total_price field for these values.

  1. 1In the No branch, click '+ Add an action'
  2. 2Search HubSpot and select 'Update contact' from HubSpot CRM (V3)
  3. 3In Contact ID, click the expression tab and type: body('Get_contact_by_email_address')?['id']
  4. 4Click '+ Add new property', select 'last_order_revenue' and set value to varOrderTotal
  5. 5Repeat for last_utm_source (varUTMSource), last_utm_campaign (varUTMCampaign), last_order_date (Parse JSON created_at)
What you should see: The Update contact card shows 4–5 property rows, each with a HubSpot property name on the left and a dynamic value or variable on the right. No red error indicators appear on any field.
Common mistake — HubSpot custom properties must exist before you can map to them here. If you type a property name that doesn't exist in your HubSpot portal, the action will silently succeed but write nothing. Create the custom contact properties in HubSpot first under Settings > Properties > Create property.
9

HubSpot action card > Sign in > HubSpot OAuth screen

Authenticate the HubSpot connection

When you first add any HubSpot action, Power Automate prompts you to sign in. Click 'Sign in' in the action card and authenticate with the HubSpot account that has Super Admin or Marketing Access permissions. Power Automate uses OAuth2 — it will redirect you to HubSpot's authorization screen. After granting access, the connection is saved and reused across all HubSpot actions in this flow.

  1. 1Click 'Sign in' in any HubSpot action card
  2. 2You are redirected to HubSpot's OAuth2 authorization page
  3. 3Select the correct HubSpot portal from the account picker if you manage multiple portals
  4. 4Click 'Grant access'
  5. 5Return to Power Automate — the connection now shows your HubSpot account email
What you should see: All HubSpot action cards in the flow show your HubSpot account email in the connection field with no warning icons.
Common mistake — If you manage multiple HubSpot portals, Power Automate will connect to whichever portal you authorize. Check the portal ID in the HubSpot URL (app.hubspot.com/contacts/PORTALID) to confirm you've connected the right one before running the flow.
10

Flow canvas > Save > Test > Manually > Trigger now

Test the flow end-to-end with a real Shopify order

Click 'Save' in the top toolbar, then click 'Test' in the upper right and choose 'Manually' to trigger a test run. Place a real test order in Shopify (use a discount code for $0 if needed) from an email address that already exists as a HubSpot contact. Watch the flow run in the 'Run history' panel — each step turns green on success or red on failure with an error message. Verify the HubSpot contact record was updated by opening it in HubSpot and checking the custom attribution properties.

  1. 1Click 'Save' in the top right toolbar
  2. 2Click 'Test', select 'Manually', click 'Test flow'
  3. 3Place a test order in Shopify using an email that exists in HubSpot
  4. 4Return to Power Automate and watch the run history — each step shows green/red status
  5. 5Open HubSpot, find the contact by email, and verify last_order_revenue and UTM fields are populated
What you should see: The run history shows all steps green. The HubSpot contact record shows updated values in last_order_revenue, last_utm_source, last_utm_campaign, and last_order_date within 2–5 minutes of the Shopify order being placed.
Power Automate
▶ Test flow
executed
HubSpot
Shopify
Shopify
🔔 notification
received
11

My flows > Flow detail page > Toggle On / Edit > + New step > Send an email (V2)

Turn on the flow and configure error notifications

Toggle the flow from Off to On using the toggle at the top of the flow detail page. Then go to 'My flows', find your flow, click the three-dot menu and select 'Edit'. Add a final 'Send an email (V2)' action configured to run after failure on any key step — specifically the HubSpot Update contact action. Set the recipient to your operations or marketing ops email so you know when an order fails to attribute. Power Automate does not retry failed webhook-triggered flows automatically.

  1. 1On the flow detail page, click the toggle to set flow status to 'On'
  2. 2Click 'Edit' to return to the canvas
  3. 3Click the three dots on the HubSpot Update contact action and select 'Configure run after'
  4. 4Check 'has failed' so this notification fires on failure
  5. 5Add 'Send an email (V2)' with subject 'Attribution flow failed for order [order id]' and your ops team email as recipient
What you should see: The flow status shows 'On' in My flows. The email notification action shows a grey run-after icon indicating it is configured to trigger only on failure of the preceding step.

Paste this expression into the Value field of the 'Initialize variable' action for varOrderTotal, or directly into the HubSpot Update contact property value field for last_order_revenue. It converts Shopify's string price to a float and rounds to 2 decimal places, then formats the order date to a HubSpot-compatible YYYY-MM-DD string — both in one expression block. Use the Expression tab (not Dynamic content) in the field picker to enter these.

JavaScript — Code Step// Expression 1: Convert Shopify total_price string to float for HubSpot Number property
▸ Show code
// Expression 1: Convert Shopify total_price string to float for HubSpot Number property
float(body('Parse_JSON')?['total_price'])
// Expression 2: Format Shopify created_at ISO timestamp to HubSpot date format (YYYY-MM-DD)

... expand to see full code

// Expression 1: Convert Shopify total_price string to float for HubSpot Number property
float(body('Parse_JSON')?['total_price'])

// Expression 2: Format Shopify created_at ISO timestamp to HubSpot date format (YYYY-MM-DD)
formatDateTime(body('Parse_JSON')?['created_at'], 'yyyy-MM-dd')

// Expression 3: Safely extract UTM source from note_attributes without looping
// Use this in a Select action to build a name-value dictionary first
json(string(body('Parse_JSON')?['note_attributes']))

// Expression 4: Null-safe UTM source extraction after Apply to each loop
if(empty(variables('varUTMSource')), 'direct', variables('varUTMSource'))

// Expression 5: Build a composite attribution label for a single HubSpot text field
concat(variables('varUTMSource'), ' / ', variables('varUTMMedium'), ' / ', variables('varUTMCampaign'))

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 Power Automate for this if your company already runs on Microsoft 365 and your data team lives in Excel, Power BI, or SharePoint. The HubSpot connector is solid, the expression language handles the UTM string parsing without extra tools, and you can connect this flow's output directly to a Power BI dataset for attribution dashboards without leaving the Microsoft ecosystem. The one scenario where you should pick something else: if you need sub-30-second attribution latency on high-volume sale days. The Shopify connector in Power Automate polls under the hood — not a true webhook push — and on busy days you can see 5–10 minute delays between an order and the HubSpot update.

Cost

On cost: the HubSpot CRM (V3) connector is a premium connector, so you need a Power Automate Per User plan at $15/user/month or a Per Flow plan at $100/month for 5 flows. Each order triggers one flow run consuming roughly 4–6 actions. At 1,000 orders/month that is well within the 250,000 action runs included in Per User plans. Compare that to Make, where the same workflow costs about 4 operations per order — 1,000 orders/month uses 4,000 operations, which fits in Make's $9/month Core plan. If you are not already in Microsoft 365, Make is cheaper by $6–$90/month depending on your flow count.

Tradeoffs

Make handles the Apply to each UTM parsing more visually — you can see each iteration in the scenario debugger, which cuts troubleshooting time significantly versus Power Automate's run history. Zapier has a native HubSpot + Shopify integration path that skips the JSON parsing entirely, but you lose control over which specific order fields get mapped — it's all or nothing on their pre-built field set. n8n lets you write the UTM extraction as a single JavaScript function node in 8 lines, eliminating the loop-and-variable pattern entirely and running faster. Pipedream gives you direct API access to both Shopify and HubSpot without connector abstraction, which matters when you need non-standard fields. Power Automate is still the right call if your ops team owns the flow — they will not touch n8n code, and they are already in Teams and SharePoint every day.

Three things you will hit after this goes live. First: Shopify's total_price is always a string in the webhook payload, not a number. The HubSpot Number property will reject it silently unless you wrap it in float() — most people discover this after a week of empty revenue fields. Second: HubSpot's OAuth token issued to Power Automate does not refresh automatically in all tenant configurations. Tokens have expired on some accounts after 60 days, breaking the flow silently until someone notices the run history shows auth errors. Set a calendar reminder to check the connection monthly. Third: if your Shopify store uses multi-currency, total_price reflects the customer's local currency, not your store's base currency. Attributing $127 CAD and $127 USD as the same revenue number will skew your HubSpot reporting. Add an order currency field and normalize in Power BI downstream.

Ideas for what to build next

  • Add lifetime revenue accumulationInstead of overwriting last_order_revenue each time, use a HubSpot 'Get contact' action before the update to read the existing total_shopify_orders value, then increment it with an add() expression before writing it back. This gives you a running total per contact without a data warehouse.
  • Build a HubSpot list by campaign for ROI reportingIn HubSpot, create Active Lists filtered by last_utm_campaign equals specific campaign names. These lists auto-update as the flow writes new attribution data, letting your marketing team see total contacts and revenue per campaign without any manual exports.
  • Add a refund reversal flowBuild a second Power Automate flow triggered by the Shopify 'When a refund is created' event that decrements last_order_revenue on the HubSpot contact. Without this, your attribution revenue figures will be overstated for campaigns with high return rates.

Related guides

Was this guide helpful?
HubSpot + Shopify overviewPower Automate profile →