

How to Track Shopify Product Interest in HubSpot with Pipedream
Captures Shopify product view events via webhook and writes browsing interest data to matching HubSpot contact properties in real time.
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 that want to feed Shopify browsing behavior into HubSpot contact records to segment and personalize campaigns without a dedicated CDP.
Not ideal for
Stores without customer accounts or login walls — anonymous browsing can't be matched to a HubSpot contact without an email identifier.
Sync type
real-timeUse case type
enrichmentReal-World Example
A 12-person DTC brand selling outdoor gear uses this workflow to append product category interests to HubSpot contacts whenever a logged-in customer views a product page on Shopify. Before this, the marketing team manually tagged contacts based on purchase history only, missing intent signals from customers who browsed but didn't buy. Now they trigger HubSpot email sequences for 'hiking gear' or 'camping equipment' within minutes of a browse event, and conversion on those sequences runs 22% higher than their generic campaigns.
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 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.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Customer Email | email | |
| Product Interests | product_interests | |
| Last Viewed Product | last_viewed_product | |
| Last Viewed Category | last_viewed_category | |
| Last Viewed At | last_viewed_at | |
3 optional fields▸ show
| Shopify Product ID | shopify_last_viewed_product_id |
| Product View Count | shopify_product_view_count |
| Shopify Customer ID | shopify_customer_id |
Step-by-Step Setup
pipedream.com > Workflows > New Workflow
Create a new Pipedream workflow
Go to pipedream.com and log in. Click 'New Workflow' in the top-right corner of the Workflows dashboard. Give it a name like 'Shopify Product Views → HubSpot Interest' so it's easy to find later. You'll land on the workflow canvas with a trigger slot waiting to be filled.
- 1Click 'New Workflow' in the top-right
- 2Enter workflow name: 'Shopify Product Views → HubSpot Interest'
- 3Click 'Create' to open the canvas
Workflow Canvas > Add a trigger > Shopify > New Event (Instant)
Set up the Shopify webhook trigger
Click the 'Add a trigger' block and search for 'Shopify'. Select the Shopify app, then choose 'New Event (Instant)' — this gives you a raw HTTP webhook endpoint that Shopify will POST to on every topic you configure. Pipedream will display a unique webhook URL immediately after you save. Copy this URL — you'll paste it into Shopify in the next step. Connect your Shopify store by clicking 'Connect Account' and authenticating with your Shopify admin credentials.
- 1Click 'Add a trigger'
- 2Search for and select 'Shopify'
- 3Choose 'New Event (Instant)' as the trigger type
- 4Click 'Connect Account' and authenticate your Shopify store
- 5Copy the generated webhook URL shown in the trigger configuration panel
Shopify Admin > Settings > Notifications > Webhooks
Register the webhook in Shopify
In your Shopify admin, go to Settings > Notifications > Webhooks and click 'Create webhook'. For standard order or customer events, you'd pick a topic from the dropdown — but for product view tracking, you'll fire the webhook from your storefront theme instead. Add the Pipedream URL to your theme's product page template so it POSTs a payload whenever a logged-in customer loads a product page. The payload should include the customer's email, the product ID, product title, and product type.
- 1Open Shopify Admin and navigate to Settings > Notifications
- 2Scroll to the Webhooks section and click 'Create webhook' for reference
- 3In your theme code (Online Store > Themes > Edit Code), open product.liquid or the relevant product template
- 4Add a fetch() call in the theme JavaScript that POSTs customer and product data to your Pipedream URL on page load
- 5Save the theme changes and test by visiting a product page while logged in as a customer
Workflow Canvas > + Add Step > Run Node.js code
Parse and validate the incoming payload
Add a new Node.js code step after the trigger. This step extracts the fields you need from the raw webhook body and throws a descriptive error if any required field is missing. This prevents bad data from reaching HubSpot. Pull out customer_email, product_id, product_title, product_type, and a timestamp. Log the parsed values using console.log so they appear in Pipedream's event inspector during testing.
- 1Click '+ Add Step' below the trigger block
- 2Select 'Run Node.js code'
- 3Paste the validation code into the code editor
- 4Click 'Test' to run against the last captured event
Workflow Canvas > + Add Step > Run Node.js code
Look up the contact in HubSpot by email
Add another Node.js code step to find the matching HubSpot contact. Use the HubSpot v3 Contacts API: GET /crm/v3/objects/contacts/search with a filter on the email property. Pass the customer_email from the previous step as the filter value. If no contact is found, export a flag (contactFound: false) so a later step can skip the update gracefully instead of throwing an error.
- 1Click '+ Add Step' and select 'Run Node.js code'
- 2Use the HubSpot Connected Account to supply the Bearer token via process.env or the $ helper
- 3Write the search API call targeting /crm/v3/objects/contacts/search
- 4Parse the response and export contactId and contactFound
- 5Click 'Test' and confirm a real contact ID appears in the exports
Workflow Canvas > + Add Step > Filter
Add a filter step to skip unmatched contacts
Add a Pipedream Filter step after the HubSpot lookup. Configure it to continue only when contactFound is true. This prevents the update step from running against a null contact ID, which would throw a 404 from HubSpot's API and mark the event as failed. For contacts that don't exist yet, you may want to add a parallel branch later that creates the contact — but start with a clean skip for now.
- 1Click '+ Add Step'
- 2Select 'Filter' from the step type list
- 3Set the condition to: steps.hubspot_lookup.contactFound === true
- 4Click 'Continue if true'
- 5Test with both a matched and unmatched email to confirm the filter behaves correctly
Workflow Canvas > + Add Step > Run Node.js code
Read the contact's existing product interests
Before writing new interest data, fetch the contact's current values for the product interest properties. Use a GET request to /crm/v3/objects/contacts/{contactId}?properties=product_interests,last_viewed_product,last_viewed_category. This prevents overwriting historical interest data with only the latest view. You'll merge the existing array with the new product type in the next step.
- 1Add a new Node.js code step
- 2Call GET /crm/v3/objects/contacts/{contactId} with the properties query param
- 3Parse the properties object from the response
- 4Export existing_interests as an array (split on semicolon if stored as a string)
- 5Test and confirm the exported array reflects whatever is already on the contact
Workflow Canvas > + Add Step > Run Node.js code
Merge and deduplicate interests
Add a Node.js code step to combine the existing interests with the new product type from the current browse event. Use a Set to deduplicate so the same category isn't appended multiple times. Then join the Set back into a semicolon-delimited string for HubSpot. Also prepare the last_viewed_product and last_viewed_at values. Export the merged string and the individual field values as separate exports for the next step.
- 1Add a new Node.js code step
- 2Import existing_interests from the previous step
- 3Spread into a Set with the new product_type and convert back to array
- 4Join with semicolons to form the merged interests string
- 5Export merged_interests, last_viewed_product, and last_viewed_at
Workflow Canvas > + Add Step > Run Node.js code
Update the HubSpot contact properties
Add a final Node.js code step to PATCH the HubSpot contact with the merged interest data. Hit /crm/v3/objects/contacts/{contactId} with a PATCH request. The body should include the product_interests, last_viewed_product, last_viewed_category, and last_viewed_at properties. Use the Connected Account OAuth token from the HubSpot app in Pipedream — reference it via auths.hubspot.oauth_access_token so you don't hard-code credentials.
- 1Add a new Node.js code step
- 2Reference the HubSpot token via auths.hubspot.oauth_access_token
- 3Build the PATCH request body with all four property fields
- 4Send the PATCH to /crm/v3/objects/contacts/{contactId}
- 5Log the response status and export the updated contact ID for confirmation
Workflow Canvas > Deploy (top-right button)
Add error handling and activate the workflow
Wrap your API calls in try/catch blocks and use $.flow.exit() to stop execution cleanly on non-retryable errors (like a 404 on a deleted contact). For 429 rate limit responses, throw an error so Pipedream's automatic retry kicks in. Once testing passes with real events, click 'Deploy' in the top-right corner to make the workflow live. Pipedream will start processing all new incoming webhook events immediately.
- 1Review each code step and add try/catch around fetch() calls
- 2For 429 responses, throw new Error('Rate limited') to trigger Pipedream retry
- 3For 404 responses, call $.flow.exit('Contact not found — skipping')
- 4Click 'Deploy' in the top-right corner
- 5Trigger a real product view on your Shopify store and confirm the HubSpot contact updates
Paste this into a single Node.js code step that handles the HubSpot lookup, interest merge, and contact update in one shot. This replaces steps 5, 7, 8, and 9 with a single step — fewer round-trips, cleaner event logs. Reference it from the step immediately after your parse/validate step.
JavaScript — Code Stepimport axios from 'axios';▸ Show code
import axios from 'axios';
export default defineComponent({
props: {... expand to see full code
import axios from 'axios';
export default defineComponent({
props: {
hubspot: {
type: 'app',
app: 'hubspot',
},
},
async run({ steps, $ }) {
const email = steps.parse_payload.$return_value.customer_email;
const productType = steps.parse_payload.$return_value.product_type;
const productTitle = steps.parse_payload.$return_value.product_title;
const shopifyCustomerId = steps.parse_payload.$return_value.customer_id;
const viewedAt = new Date(steps.parse_payload.$return_value.viewed_at).getTime();
const baseURL = 'https://api.hubapi.com';
const headers = {
Authorization: `Bearer ${this.hubspot.$auth.oauth_access_token}`,
'Content-Type': 'application/json',
};
// Step 1: Find contact by email
const searchRes = await axios({
method: 'POST',
url: `${baseURL}/crm/v3/objects/contacts/search`,
headers,
data: {
filterGroups: [{
filters: [{
propertyName: 'email',
operator: 'EQ',
value: email.trim().toLowerCase(),
}],
}],
properties: ['email', 'product_interests', 'shopify_product_view_count'],
limit: 1,
},
});
if (searchRes.data.total === 0) {
$.flow.exit(`No HubSpot contact found for email: ${email}`);
}
const contact = searchRes.data.results[0];
const contactId = contact.id;
const rawInterests = contact.properties.product_interests || '';
const currentViewCount = parseInt(contact.properties.shopify_product_view_count || '0', 10);
// Step 2: Merge and deduplicate interests
const existingInterests = rawInterests.split(';').filter(Boolean);
const mergedInterests = [...new Set([...existingInterests, productType])].join(';');
const newViewCount = currentViewCount + 1;
// Step 3: PATCH the contact
const patchRes = await axios({
method: 'PATCH',
url: `${baseURL}/crm/v3/objects/contacts/${contactId}`,
headers,
data: {
properties: {
product_interests: mergedInterests,
last_viewed_product: productTitle,
last_viewed_category: productType,
last_viewed_at: viewedAt,
shopify_customer_id: shopifyCustomerId,
shopify_product_view_count: newViewCount.toString(),
},
},
});
if (patchRes.status !== 200) {
throw new Error(`HubSpot PATCH failed with status ${patchRes.status}`);
}
return {
contactId,
updatedInterests: mergedInterests,
viewCount: newViewCount,
status: 'updated',
};
},
});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 Pipedream for this if your team includes at least one developer comfortable with Node.js, because the product view tracking setup requires custom theme code in Shopify that no no-code platform can generate for you. Pipedream's instant webhook processing means browse events hit HubSpot in under 3 seconds — fast enough to matter if you're triggering follow-up emails within the same session. The Node.js code steps also let you handle the semicolon-delimited HubSpot property format and deduplication logic cleanly in one place. The one case where you'd pick something else: if your team has zero coding capacity, you'd be better off using a dedicated product analytics tool like Klaviyo (which has a native Shopify integration that tracks product views without custom code) rather than trying to build this on any general-purpose automation platform.
Pipedream's pricing is credit-based: each workflow execution costs 1 credit. On the free tier you get 10,000 credits per month — roughly 333 executions per day. A store with 500 logged-in customers viewing 3 products each per day burns 1,500 credits daily, which exceeds the free tier. At that volume you need Pipedream's Basic plan at $29/month, which includes 100,000 credits. Make's equivalent free tier handles 1,000 operations per month (far less), and their Core plan at $9/month gives 10,000 — still not enough for 1,500/day. Zapier's free tier caps at 100 tasks/month. For this volume, Pipedream is cheaper than Zapier by roughly $40/month and has no meaningful disadvantage over Make for a developer-built workflow.
Make has a better visual debugger for tracing exactly which step failed and what the payload looked like at that moment — Pipedream's event inspector is good but Make's is more intuitive for non-developers reviewing errors. Zapier can't do this workflow at all without Code by Zapier, and even then you can't fire a webhook from a Shopify theme into a Zap trigger reliably. n8n handles the deduplication logic just as well as Pipedream and costs less if you self-host, but you'd need to manage infrastructure. Power Automate has no meaningful Shopify connector for product-level events. Pipedream wins here because the combination of instant webhook ingestion, Node.js code steps, and HubSpot OAuth in one platform eliminates the need for any intermediate tool — you write the logic once and it runs.
Three things you'll hit after setup. First: Shopify's customer object in the storefront includes the customer email, but if you're using Shopify's new customer account URLs (accounts.shopify.com instead of yourstore.myshopify.com/account), the Liquid customer object behaves differently — test your {% if customer %} conditional against both account URL formats before going live. Second: HubSpot's contact search API occasionally returns stale index data for contacts updated in the last 60 seconds, so if a contact was just created via another workflow milliseconds before a product view fires, the search may return zero results. Add a 1-second delay before the lookup if you're creating contacts and tracking views in the same session. Third: if a customer views 10 products in rapid succession (say, during a wishlist browsing session), your workflow will fire 10 concurrent executions and all 10 will read the existing interests list before any of them write back — causing the last write to overwrite earlier ones. Solve this by appending interests in a Pipedream data store as an atomic operation rather than reading, merging, and writing in one round-trip.
Ideas for what to build next
- →Enroll contacts in HubSpot sequences based on interest score — Once product_interests is populated, add a HubSpot workflow that auto-enrolls contacts into email sequences when their interest list includes specific categories. Contacts who viewed 'Hiking Packs' 3+ times in 7 days should get a different sequence than first-time browsers.
- →Add a Shopify abandoned cart signal to the same contact — Extend this Pipedream workflow — or build a parallel one — that fires when a Shopify checkout is created but not completed. Write the cart value and SKUs to HubSpot alongside the interest data so sales reps can see both browse intent and cart abandonment on one contact record.
- →Build a weekly interest digest to Slack — Add a second Pipedream workflow on a daily cron schedule that queries HubSpot for contacts whose product_interests were updated in the last 24 hours and posts a summary to a Slack channel. This gives the marketing team a daily read on which categories are trending before they plan campaign sends.
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