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

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-time

Use case type

sync

Real-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.

/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 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.

Shopify Admin access with permission to manage webhooks under Settings > Notifications
HubSpot account with a private app token that has scopes: crm.objects.contacts.read and crm.objects.contacts.write
n8n instance accessible via a public URL (not localhost) so Shopify can deliver webhooks
Custom HubSpot contact properties created for shopify_customer_id (single-line text) and last_order_date (date picker) before mapping
HubSpot Private App created under Settings > Integrations > Private Apps with the required CRM scopes enabled

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Email Addressemail
First Namefirstname
Last Namelastname
Shopify Customer IDshopify_customer_id
6 optional fields▸ show
Phone Numberphone
Last Order Datelast_order_date
Total Spenttotal_revenue
Citycity
Countrycountry
Number of Ordersnum_associated_deals

Step-by-Step Setup

1

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.

  1. 1Click '+ New Workflow' in the top-right corner
  2. 2Click the pencil icon next to 'My Workflow' and rename it to 'Shopify → HubSpot Customer Sync'
  3. 3Click the empty trigger node in the center of the canvas to open the node selector
What you should see: You should see an empty canvas with one placeholder trigger node and the workflow name updated in the header.
2

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.

  1. 1Type 'Shopify' in the node search box
  2. 2Click 'Shopify Trigger' from the results list
  3. 3Set the 'Topic' dropdown to 'orders/create'
  4. 4Click 'Copy Webhook URL' and save it to a text editor
What you should see: The Shopify Trigger node should show 'orders/create' as the selected topic and display a webhook URL ending in '/webhook'.
Common mistake — The webhook URL is only valid after you save the workflow. If you register it in Shopify before saving, Shopify will get a 404 on its verification request and reject the webhook.
n8n
+
click +
search apps
HubSpot
HU
HubSpot
Add a Shopify webhook trigger
HubSpot
HU
module added
3

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.

  1. 1Go to Shopify Admin and click 'Settings' in the bottom-left sidebar
  2. 2Click 'Notifications', then scroll to the 'Webhooks' section at the bottom
  3. 3Click 'Create webhook'
  4. 4Set Event to 'Order creation', Format to 'JSON', and paste the n8n URL
  5. 5Set API version to '2024-01' and click 'Save webhook'
What you should see: Shopify should show a green 'Webhook saved' confirmation and list your new webhook under the Webhooks section with status 'Active'.
Common mistake — Shopify sends the verification ping synchronously when you hit Save. If your n8n instance is behind a firewall or on localhost, Shopify will reject it. Use ngrok or deploy n8n to a public URL before this step.
4

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.

  1. 1Click the Shopify Trigger node to open its panel
  2. 2Click 'Listen for Test Event' — the node enters waiting state
  3. 3In Shopify Admin, go to Orders > Create Order, add a product, apply Bogus Gateway, and complete checkout
  4. 4Return to n8n and confirm the payload has arrived
What you should see: The Shopify Trigger node should show a green checkmark and display the full JSON payload including customer, billing_address, and line_items arrays.
Common mistake — Shopify's Bogus Gateway is only available in development stores or stores that have never processed a real transaction. On a live store, create a real $0.01 test order using a physical card and immediately refund it.
n8n
▶ Run once
executed
HubSpot
Shopify
Shopify
🔔 notification
received
5

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.

  1. 1Click the '+' connector after the Shopify Trigger node
  2. 2Search for 'Code' and select the Code node
  3. 3Set the language to 'JavaScript'
  4. 4Paste the transformation code into the editor
  5. 5Click 'Test Step' to run it against the sample payload
What you should see: The Code node output panel should show a clean JSON object with fields like email, firstname, lastname, phone, shopify_customer_id, total_spent as a number, and last_order_date as an ISO string.
Common mistake — Shopify sends total_price as a string, not a number. If you map it directly to a HubSpot number property without parsing it, HubSpot will reject the update with a 400 error referencing invalid property value type.

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 }];
6

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.

  1. 1Click '+' after the Code node and search for 'HubSpot'
  2. 2Select the HubSpot node, set Resource to 'Contact', Operation to 'Search'
  3. 3Under Filters, set Property Name to 'email', Operator to 'Equal to'
  4. 4Set the Value field to the email expression from the Code node: {{ $json.email }}
  5. 5Click 'Test Step' to confirm it returns results or an empty array
What you should see: If the contact exists, you'll see a JSON object with the contact's HubSpot ID (vid) and current properties. If not, the results array will be empty — both outcomes are correct at this stage.
7

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.

  1. 1Click '+' after the HubSpot Search node and add an IF node
  2. 2Set Condition type to 'Number'
  3. 3Set Value 1 to {{ $node['HubSpot'].json.total }}
  4. 4Set Operator to 'Greater Than', Value 2 to 0
  5. 5Confirm the node shows two output connectors labeled 'true' and 'false'
What you should see: The IF node should display two output branches. The 'true' branch glows when a contact was found; the 'false' branch glows when the search returned zero results.
Common mistake — HubSpot's Search API returns a 'total' field at the top level of the response, not inside the 'results' array. If you accidentally reference $json.results.length, it will always evaluate to false even when contacts exist.
8

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.

  1. 1Drag a new HubSpot node from the node palette onto the canvas
  2. 2Connect the 'false' output of the IF node to this HubSpot node
  3. 3Set Resource to 'Contact', Operation to 'Create'
  4. 4Map email, firstname, lastname, phone from {{ $node['Code'].json.email }} etc.
  5. 5Add Additional Fields for shopify_customer_id and last_order_date
What you should see: After running a test with a new email address, the HubSpot node should return a response containing a new contact ID (vid) and all the properties you mapped.
Common mistake — HubSpot requires email to be lowercase. If Shopify sends '[email protected]', HubSpot will create a duplicate when the same customer later checks out with '[email protected]'. Normalize to lowercase in the Code node before this step.
9

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.

  1. 1Drag another HubSpot node onto the canvas
  2. 2Connect the 'true' output of the IF node to this node
  3. 3Set Resource to 'Contact', Operation to 'Update'
  4. 4Set Contact ID to {{ $node['HubSpot'].json.results[0].id }}
  5. 5Map firstname, lastname, phone, shopify_customer_id, total_spent, last_order_date from the Code node
What you should see: Running a test with an existing contact's email should return the same contact ID that the Search node found, confirming the correct record was updated.
Common mistake — Do not map email in the Update node unless you have a deliberate reason to change it. Overwriting email on an update can break HubSpot list membership and de-enroll contacts from active email sequences mid-flight.
10

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.

  1. 1Click the three-dot menu on the HubSpot Create node and toggle 'Continue on Fail' on
  2. 2Do the same for the HubSpot Update node
  3. 3Click the workflow name in the top bar, then 'Workflow Settings'
  4. 4Under 'Error Workflow', select or create a separate error-notification workflow
  5. 5Save the workflow
What you should see: Both HubSpot nodes should show a small warning triangle icon indicating 'Continue on Fail' is active. The Workflow Settings panel should display your error workflow name under Error Workflow.
11

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.

  1. 1Click the 'Inactive' toggle in the top-right to switch it to 'Active'
  2. 2Place a test order in Shopify using a real or test email
  3. 3Click 'Executions' in the n8n left sidebar
  4. 4Click the most recent execution and confirm every node shows a green status
  5. 5Open HubSpot Contacts and search for the test email to verify the record
What you should see: The Executions log should show a completed run with all nodes green. The HubSpot contact should have accurate firstname, lastname, phone, shopify_customer_id, and last_order_date values matching the Shopify order.
Common mistake — Guest checkouts in Shopify do not create a customer record — they only generate an order. The webhook still fires, but customer.id will be null. Add a null check in the Code node to skip or handle these gracefully, otherwise the HubSpot Search node will throw an error on a blank email.

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 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.

Cost

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.

Tradeoffs

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 ShopifyTurn 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 dataExtend 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 enrollmentAfter 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

Was this guide helpful?
HubSpot + Shopify overviewn8n profile →