

How to Sync Shopify Customers to HubSpot with n8n
Automatically creates or updates a HubSpot contact every time a customer registers or places an order in Shopify, keeping purchase history and contact details in sync for marketing use.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
E-commerce teams who want every Shopify buyer to appear in HubSpot automatically so they can run post-purchase email campaigns without manual CSV exports.
Not ideal for
Stores with over 5,000 orders per day — at that volume, use a dedicated ETL tool like Fivetran with a HubSpot data warehouse sync instead.
Sync type
real-timeUse case type
syncReal-World Example
A 12-person DTC apparel brand was exporting Shopify customer CSVs every Monday and importing them into HubSpot by hand — a 2-hour task that meant new buyers waited up to 7 days before entering any email flow. After setting up this workflow, every new Shopify order creates or updates a HubSpot contact within 30 seconds, and the marketing team can trigger welcome sequences the same day someone buys.
What Will This Cost?
Drag the slider to your expected monthly volume.
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
Import this workflow directly into n8n
Copy the pre-built n8n blueprint and paste it straight into n8n. 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.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Email Address | email | |
| First Name | firstname | |
| Last Name | lastname | |
| Shopify Customer ID | shopify_customer_id | |
6 optional fields▸ show
| Phone Number | phone |
| Last Order Date | last_order_date |
| Total Spent | total_revenue |
| City | city |
| Country | country |
| Number of Orders | num_associated_deals |
Step-by-Step Setup
n8n Dashboard > Workflows > + New Workflow
Create a new workflow in n8n
Log into your n8n instance and click the orange '+ New Workflow' button in the top-right corner of the Workflows dashboard. Give the workflow a clear name like 'Shopify → HubSpot Customer Sync' so it's easy to find later. You'll land on the canvas with a single empty trigger node waiting to be configured. n8n saves drafts automatically, so you won't lose progress if you close the tab.
- 1Click '+ New Workflow' in the top-right corner
- 2Click the pencil icon next to 'My Workflow' and rename it to 'Shopify → HubSpot Customer Sync'
- 3Click the empty trigger node in the center of the canvas to open the node selector
Canvas > Trigger Node > Search 'Shopify' > Shopify Trigger
Add a Shopify webhook trigger
In the node selector panel, search for 'Shopify' and select the Shopify Trigger node. Set the event to 'Order Created' — this fires whenever a customer completes a purchase, which also captures new customer registrations made during checkout. n8n will generate a unique webhook URL for this trigger; copy it immediately because you'll paste it into Shopify in the next step. Do not use the polling-based Shopify node here — webhook delivery is near-instant while polling checks every 15 minutes at best.
- 1Type 'Shopify' in the node search box
- 2Click 'Shopify Trigger' from the results list
- 3Set the 'Topic' dropdown to 'orders/create'
- 4Click 'Copy Webhook URL' and save it to a text editor
Shopify Admin > Settings > Notifications > Webhooks > Create Webhook
Register the webhook in Shopify
In your Shopify Admin, navigate to Settings > Notifications and scroll to the bottom of the page to find the Webhooks section. Click 'Create webhook', set the Event to 'Order creation', the Format to 'JSON', and paste the n8n webhook URL into the URL field. Set the API version to the latest stable release (currently 2024-01). Click 'Save webhook' — Shopify will immediately send a test POST to verify the URL is reachable, so your n8n workflow must be active and saved before this step.
- 1Go to Shopify Admin and click 'Settings' in the bottom-left sidebar
- 2Click 'Notifications', then scroll to the 'Webhooks' section at the bottom
- 3Click 'Create webhook'
- 4Set Event to 'Order creation', Format to 'JSON', and paste the n8n URL
- 5Set API version to '2024-01' and click 'Save webhook'
n8n Canvas > Shopify Trigger Node > Listen for Test Event
Test the trigger with a real Shopify event
Back in n8n, click 'Listen for Test Event' on the Shopify Trigger node. Then in Shopify, go to your store's admin and place a test order using Shopify's 'Bogus Gateway' payment method — this simulates a real order without charging a card. Within 5-10 seconds, n8n should receive the webhook payload. Click through the received data to confirm you can see fields like customer.email, customer.first_name, customer.last_name, and line_items. This sample data will autocomplete field references in all downstream nodes.
- 1Click the Shopify Trigger node to open its panel
- 2Click 'Listen for Test Event' — the node enters waiting state
- 3In Shopify Admin, go to Orders > Create Order, add a product, apply Bogus Gateway, and complete checkout
- 4Return to n8n and confirm the payload has arrived
Canvas > + after Trigger > Search 'Code' > Code Node
Add a Code node to extract and clean customer data
Click the '+' button after the trigger node and add a 'Code' node. This is where you extract the fields you need and normalize them before they hit HubSpot. Paste the transformation code from the Pro Tip section below into the JavaScript editor. The code pulls customer email, name, phone, and Shopify customer ID, converts total_spent from Shopify's string format (e.g. '149.99') to a float, and builds the last_order_date in ISO format that HubSpot expects. Run the node against your test data to confirm the output shape before moving on.
- 1Click the '+' connector after the Shopify Trigger node
- 2Search for 'Code' and select the Code node
- 3Set the language to 'JavaScript'
- 4Paste the transformation code into the editor
- 5Click 'Test Step' to run it against the sample payload
Paste this into the Code node (Step 5) positioned between the Shopify Trigger and the HubSpot Search node. It extracts and normalizes all customer fields, handles guest checkout null cases, converts Shopify's string prices to floats, and formats dates to the ISO 8601 'YYYY-MM-DD' format HubSpot's date picker properties require.
JavaScript — Code Node// n8n Code Node — Shopify to HubSpot Customer Data Transformer▸ Show code
// n8n Code Node — Shopify to HubSpot Customer Data Transformer // Position: between Shopify Trigger and HubSpot Search node const order = items[0].json;
... expand to see full code
// n8n Code Node — Shopify to HubSpot Customer Data Transformer
// Position: between Shopify Trigger and HubSpot Search node
const order = items[0].json;
const customer = order.customer;
// Guard: skip guest checkouts with no customer email
if (!customer || !customer.email) {
// Return empty array — n8n will stop this execution branch cleanly
return [];
}
// Normalize email to lowercase to prevent HubSpot duplicates
const email = customer.email.toLowerCase().trim();
// Parse total_price from string to float (Shopify always sends as string)
const totalSpent = parseFloat(order.total_price) || 0;
// Format order date to YYYY-MM-DD for HubSpot date picker properties
const rawDate = new Date(order.created_at);
const lastOrderDate = rawDate.toISOString().split('T')[0]; // e.g. '2024-03-14'
// Extract billing address fields (may be null for digital-only orders)
const billing = order.billing_address || {};
// Build the normalized output object
const normalized = {
email: email,
firstname: customer.first_name || '',
lastname: customer.last_name || '',
phone: customer.phone || billing.phone || '',
shopify_customer_id: String(customer.id),
last_order_date: lastOrderDate,
total_spent: totalSpent,
orders_count: customer.orders_count || 1,
city: billing.city || '',
country: billing.country || '',
// Flag for downstream logic: is this a repeat buyer?
is_repeat_customer: (customer.orders_count || 1) > 1,
};
return [{ json: normalized }];Canvas > + after Code Node > HubSpot > Contact > Search
Search HubSpot for an existing contact
Add a HubSpot node after the Code node. Set the Resource to 'Contact' and the Operation to 'Search'. Use the customer's email address from the Code node output as the search filter — set Property to 'email', Operator to 'Equal to', and Value to the email expression. This step determines whether the customer already exists in HubSpot so you can either create a new contact or update the existing one. HubSpot's contact search returns the full contact record including the internal contact ID (vid) which you'll need for the update path.
- 1Click '+' after the Code node and search for 'HubSpot'
- 2Select the HubSpot node, set Resource to 'Contact', Operation to 'Search'
- 3Under Filters, set Property Name to 'email', Operator to 'Equal to'
- 4Set the Value field to the email expression from the Code node: {{ $json.email }}
- 5Click 'Test Step' to confirm it returns results or an empty array
Canvas > + after HubSpot Search > IF Node
Add an IF node to branch on contact existence
Add an IF node after the HubSpot Search node. Set the condition to check whether the search returned any results: use the expression {{ $json.total }} and check if it is greater than 0. The 'true' branch handles existing contacts (update path), and the 'false' branch handles new contacts (create path). This prevents creating duplicate contacts in HubSpot when a customer makes their second or third purchase. Connect both branches to their respective HubSpot operations in the next two steps.
- 1Click '+' after the HubSpot Search node and add an IF node
- 2Set Condition type to 'Number'
- 3Set Value 1 to {{ $node['HubSpot'].json.total }}
- 4Set Operator to 'Greater Than', Value 2 to 0
- 5Confirm the node shows two output connectors labeled 'true' and 'false'
Canvas > IF Node (false branch) > HubSpot > Contact > Create
Create a new HubSpot contact (false branch)
Connect the 'false' output of the IF node to a new HubSpot node. Set Resource to 'Contact' and Operation to 'Create'. Map each field from the Code node output to the corresponding HubSpot property using the field mapping table below. At minimum, set email, firstname, lastname, phone, and the custom properties for shopify_customer_id and last_order_date. Make sure you reference the Code node output using $node['Code'].json.fieldname rather than the HubSpot Search node output — the data path here comes from two nodes back.
- 1Drag a new HubSpot node from the node palette onto the canvas
- 2Connect the 'false' output of the IF node to this HubSpot node
- 3Set Resource to 'Contact', Operation to 'Create'
- 4Map email, firstname, lastname, phone from {{ $node['Code'].json.email }} etc.
- 5Add Additional Fields for shopify_customer_id and last_order_date
Canvas > IF Node (true branch) > HubSpot > Contact > Update
Update an existing HubSpot contact (true branch)
Connect the 'true' output of the IF node to another HubSpot node. Set Resource to 'Contact' and Operation to 'Update'. For the Contact ID field, use the HubSpot Search node's result: {{ $node['HubSpot'].json.results[0].id }}. Map the same fields as the Create node — this overwrites outdated phone numbers, addresses, and purchase data with the latest values from Shopify. You do not need to re-send email on an update since HubSpot uses the contact ID to target the record.
- 1Drag another HubSpot node onto the canvas
- 2Connect the 'true' output of the IF node to this node
- 3Set Resource to 'Contact', Operation to 'Update'
- 4Set Contact ID to {{ $node['HubSpot'].json.results[0].id }}
- 5Map firstname, lastname, phone, shopify_customer_id, total_spent, last_order_date from the Code node
Workflow Settings > Error Workflow | Node Settings > Continue on Fail
Add error handling with a catch node
Click the settings gear on both HubSpot nodes (Create and Update) and enable 'Continue on Fail'. Then add an Error Trigger node connected to a Slack or email notification node — this catches any execution that fails at the HubSpot API level and alerts your team before the data gap grows. Without this, a HubSpot API outage or rate limit breach will silently drop customer records and you won't know until your CRM is visibly stale. In n8n, you configure this under Workflow Settings > Error Workflow by pointing to a separate error-handling workflow.
- 1Click the three-dot menu on the HubSpot Create node and toggle 'Continue on Fail' on
- 2Do the same for the HubSpot Update node
- 3Click the workflow name in the top bar, then 'Workflow Settings'
- 4Under 'Error Workflow', select or create a separate error-notification workflow
- 5Save the workflow
n8n Canvas > Activate Toggle (top-right) | Left Sidebar > Executions
Activate the workflow and verify end-to-end
Toggle the workflow from 'Inactive' to 'Active' using the switch in the top-right corner of the canvas. Place another test order in Shopify and watch the Executions log in n8n (left sidebar > Executions) to confirm all nodes run green. Check HubSpot Contacts to verify the new record appears with correct field values. Set a calendar reminder to review the Executions log after 48 hours of live traffic — you'll quickly spot any edge cases like orders with missing customer emails (guest checkouts) that need additional handling.
- 1Click the 'Inactive' toggle in the top-right to switch it to 'Active'
- 2Place a test order in Shopify using a real or test email
- 3Click 'Executions' in the n8n left sidebar
- 4Click the most recent execution and confirm every node shows a green status
- 5Open HubSpot Contacts and search for the test email to verify the record
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
Use n8n for this if you want full control over the data transformation between Shopify and HubSpot without paying per-task fees. The Code node gives you a real JavaScript environment — you can normalize emails, parse Shopify's string prices, handle null guest checkout fields, and format dates exactly the way HubSpot expects, all in one step. n8n is also the right call if you're self-hosting and need to keep customer data off third-party servers for compliance reasons. The one scenario where you'd skip n8n: if your team has zero developers and nobody wants to maintain a JavaScript node — use Zapier's Shopify + HubSpot integration instead, accept the field mapping limitations, and move on.
n8n's self-hosted version costs nothing in per-execution fees. You pay for the server (a $12/month DigitalOcean droplet handles this comfortably at up to 5,000 orders/month) and that's it. Zapier charges per task — at 1,000 orders/month with 3 tasks per execution (trigger, search, create/update), that's 3,000 tasks/month, which puts you on the $49/month Starter plan and you'd still hit the task cap during a sale. Make's free tier allows 1,000 operations/month, which covers about 300 orders given 3 operations per run. n8n cloud starts at $20/month with no per-execution fee. At 1,000+ orders/month, n8n is $29-37/month cheaper than Zapier.
Zapier's HubSpot integration has a native 'Find or Create Contact' action that collapses the Search + IF + Create/Update into a single step — genuinely simpler setup than n8n's 3-node equivalent. Make's Shopify module supports watching for both customer creation and order events in a single trigger, which is cleaner than n8n's orders/create-only approach. Power Automate has pre-built Shopify and HubSpot connectors but both require premium licenses ($15/user/month) and the HubSpot connector lags behind the API by several versions. Pipedream offers a HubSpot SDK with built-in deduplication helpers that save you writing the IF node logic manually. n8n is still the right call when you need the Code node's flexibility, you're self-hosting for data sovereignty, or you're already running other n8n workflows and don't want another tool in the stack.
Three things you'll hit after going live. First, Shopify's webhook delivery is not guaranteed — if your n8n instance is unreachable for any reason, Shopify retries for 48 hours then permanently disables the webhook and sends you an email. Check the webhook status in Shopify Admin weekly. Second, HubSpot's date picker properties require the date as a Unix timestamp in milliseconds, not an ISO string — despite the API docs being ambiguous on this. If last_order_date isn't saving correctly, change the Code node to output new Date(order.created_at).getTime() instead of the ISO string. Third, Shopify sends orders_count as the total historical count, not the count for the current session — so a customer on their 10th purchase will have orders_count: 10, which is what you want for HubSpot segmentation, but it can be surprising if you assumed it was always 1 for a new order event.
Ideas for what to build next
- →Sync HubSpot contact updates back to Shopify — Turn this into a two-way sync by adding a HubSpot webhook trigger that fires when a contact property changes (e.g. marketing opt-out) and pushes that update back to the Shopify customer record via the Shopify API.
- →Enrich contacts with Shopify product data — Extend the Code node to extract line_items from the order payload and write the product category or SKU to a custom HubSpot property, enabling product-based segmentation for targeted campaigns.
- →Add a post-purchase HubSpot workflow enrollment — After the HubSpot contact is created or updated, use the HubSpot node to enroll the contact in a specific workflow (e.g. a post-purchase review request sequence) by calling the Workflow Enrollments API with the contact ID.
Related guides
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup
How to Share Notion Meeting Notes to Slack with n8n
~20 min setup
How to Send Notion Meeting Notes to Slack with Zapier
~8 min setup
How to Share Notion Meeting Notes to Slack with Make
~12 min setup
How to Create Notion Tasks from Slack with Pipedream
~15 min setup