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

How to Sync Shopify Revenue Data to HubSpot for Attribution with Pipedream

When a Shopify order is placed, Pipedream pulls the customer's HubSpot contact, matches the order to the originating campaign or traffic source, and writes revenue attribution data back to HubSpot deal and contact properties.

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

Best for

E-commerce marketers who need to see exactly which HubSpot campaigns, email sends, or ad sources drove Shopify revenue — without exporting CSVs manually.

Not ideal for

Stores under 100 orders/month with no HubSpot campaign tracking set up yet — start with HubSpot's native Shopify integration first and graduate to Pipedream when you need custom attribution logic.

Sync type

real-time

Use case type

reporting

Real-World Example

💡

A 12-person DTC apparel brand runs 6-8 HubSpot email campaigns per month and spends $40K/month on paid ads tracked through HubSpot UTM parameters. Before this workflow, their marketing analyst spent 4 hours every Monday joining Shopify export CSVs with HubSpot campaign reports in Excel to figure out which campaign drove last week's $180K in orders. With Pipedream firing on every Shopify order webhook, HubSpot contact properties like 'Last Campaign Attributed Revenue' and 'Lifetime Order Value' update within 30 seconds of purchase — and the weekly report now takes 15 minutes to pull.

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.

HubSpot account with API access — requires at least a Starter plan; the free CRM tier does not support private app API tokens
HubSpot Private App token with scopes: crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.deals.read, crm.objects.deals.write
Shopify store with Admin API access — requires a custom app with read_orders and write_orders scopes enabled in Shopify Partners or store admin
Custom HubSpot contact properties already created: last_order_revenue (number), last_attributed_campaign (single-line text), last_purchase_date (date), shopify_order_id (single-line text)
Pipedream account — free tier works for under 10,000 orders/month; Basic plan ($19/month) removes the 30-second execution timeout if you add heavy processing

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Customer Emailemail
Order Total Revenueamount
Shopify Order IDshopify_order_id
UTM Campaignlast_attributed_campaign
Order Created Datelast_purchase_date
Deal Close Dateclosedate
Shopify Order Namedealname
4 optional fields▸ show
UTM Sourcehs_analytics_source
UTM Mediumhs_analytics_source_data_1
Product Nameshs_note_body
First Order Flaghs_is_first_order

Step-by-Step Setup

1

pipedream.com > Workflows > New Workflow

Create a new Pipedream workflow

Go to pipedream.com and click 'New Workflow' in the top left of the Workflows dashboard. Give the workflow a name like 'Shopify → HubSpot Revenue Attribution' so it's findable later. You'll land on the workflow canvas with an empty trigger slot at the top. This is where you'll connect Shopify's order webhook in the next step.

  1. 1Click 'New Workflow' in the top left
  2. 2Enter a descriptive workflow name in the name field
  3. 3Click 'Save' to confirm the name before adding a trigger
What you should see: You should see a blank workflow canvas with a gray 'Add a trigger' block at the top and no steps below it yet.
2

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

Add the Shopify 'New Order' webhook trigger

Click the 'Add a trigger' block and search for 'Shopify' in the app search box. Select the Shopify app and choose 'New Order (Instant)' as the trigger — this fires via webhook the moment an order is created in Shopify, not on a polling schedule. Connect your Shopify store by selecting or adding a Connected Account. Pipedream will register a webhook directly in your Shopify store's admin under Settings > Notifications.

  1. 1Click the 'Add a trigger' block
  2. 2Type 'Shopify' in the search box and select the Shopify app
  3. 3Select 'New Order (Instant)' from the trigger list
  4. 4Click 'Connect a Shopify account' and authorize with your store credentials
  5. 5Click 'Save and continue'
What you should see: The trigger block should show a green 'Connected' badge and a sample order payload in the event inspector on the right side. You should see fields like order.id, order.email, order.total_price, and order.source_name in the payload.
Common mistake — Shopify only fires 'New Order' webhooks for orders created after the webhook is registered. Pipedream does not backfill historical orders. If you need historical data, you'll need a separate one-time bulk import step — this workflow only handles net-new orders going forward.
Pipedream
+
click +
search apps
HubSpot
HU
HubSpot
Add the Shopify 'New Order' …
HubSpot
HU
module added
3

Workflow Canvas > + Add a step > Custom Code (Node.js)

Extract UTM and traffic source from the Shopify order

Add a Node.js code step immediately after the trigger. Shopify stores UTM attribution data in the order's landing_site and referring_site fields, and in note_attributes if you're passing UTM params through checkout. Write a code step to parse these fields and extract utm_source, utm_medium, utm_campaign, and utm_content so they're available as clean variables for HubSpot in later steps. Without this normalization step, the raw Shopify landing_site field is a full URL string — not useful for matching against HubSpot campaign names.

  1. 1Click '+ Add a step' below the trigger
  2. 2Select 'Custom Code' and choose 'Node.js'
  3. 3Paste your UTM parsing logic into the code editor (see Pro Tip below)
  4. 4Click 'Test' to run the step against the sample order payload
  5. 5Verify the parsed UTM fields appear in the step's output in the right panel
What you should see: The step output in the right panel should show a clean object with keys like utm_source: 'google', utm_medium: 'cpc', utm_campaign: 'spring-sale-2024', and order_revenue: '149.99'.
Common mistake — Shopify's landing_site field is only populated when the customer's first session came from an external source. For direct traffic or returning customers who typed the URL, landing_site will be null. Your code step must handle null gracefully or downstream HubSpot writes will fail.
4

Workflow Canvas > + Add a step > HubSpot > Find Contact by Email

Look up the HubSpot contact by email

Add a Pipedream HubSpot step to find the contact record that matches the Shopify order's customer email. Select the 'Search Contacts' or 'Get Contact by Email' action. Map the email field from the Shopify trigger payload (steps.trigger.event.customer.email) into the email search parameter. This step returns the full HubSpot contact object, including the contact's original traffic source, first conversion, and any campaign memberships HubSpot has tracked.

  1. 1Click '+ Add a step' and search for 'HubSpot'
  2. 2Select the 'Find Contact' or 'Search Contacts' action
  3. 3Connect your HubSpot account via Connected Accounts
  4. 4Set the email parameter to {{steps.trigger.event.customer.email}}
  5. 5Click 'Test' to confirm the contact is found
What you should see: The step output should show a HubSpot contact object with the contact's ID (e.g. hs_object_id: 51204703), lifecycle stage, first touch source, and any custom attribution properties you've already set on the contact.
Common mistake — If the customer checked out as a guest or used a different email than the one in HubSpot, this step will return an empty result. Add a conditional branch after this step to handle 'contact not found' — either create a new contact or log the unmatched order to a separate dataset.
5

Workflow Canvas > + Add a step > HubSpot > Create Deal

Create or update a HubSpot deal for the order

Add a HubSpot 'Create Deal' or 'Update Deal' step. You'll create one deal per Shopify order, representing it as a closed-won revenue event tied to the contact. Map the Shopify order total to the deal's amount field, the order ID to a custom deal property (shopify_order_id), and the parsed UTM campaign name to the deal's hs_analytics_source_data_1 or a custom attribution field. Associate the deal with the contact found in the previous step using the HubSpot contact ID from step 4.

  1. 1Click '+ Add a step' and select HubSpot
  2. 2Choose 'Create Deal' from the action list
  3. 3Set 'Deal Name' to the Shopify order name (e.g. '#1042 - [email protected]')
  4. 4Map 'Amount' to {{steps.trigger.event.total_price}}
  5. 5Set 'Pipeline Stage' to your 'Closed Won' stage ID
  6. 6Add the contact association using the ID from step 4
  7. 7Map your custom shopify_order_id property to {{steps.trigger.event.id}}
What you should see: A new deal should appear in HubSpot under the matched contact record, showing the correct dollar amount, the Shopify order name, and a Closed Won stage. You can verify by opening HubSpot CRM > Deals and filtering by the deal creation date.
Common mistake — HubSpot pipeline stage IDs are not human-readable — you must look up the exact stage ID from HubSpot Settings > CRM > Pipelines. Using the stage name string directly in the API call will fail silently or create the deal in the wrong stage.
6

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

Update HubSpot contact attribution properties

Add a HubSpot 'Update Contact' step to write revenue attribution data back to the contact record itself. This is separate from the deal and gives you contact-level aggregate metrics. Update properties like last_order_revenue (the Shopify order total), last_attributed_campaign (the UTM campaign from step 3), last_purchase_date (the Shopify order created_at timestamp), and lifetime_order_value if you're computing a running total. Use the contact ID from step 4 as the record identifier.

  1. 1Click '+ Add a step' and select HubSpot
  2. 2Choose 'Update Contact'
  3. 3Set the Contact ID to {{steps.find_contact.contact.id}}
  4. 4Map last_order_revenue to {{steps.trigger.event.total_price}}
  5. 5Map last_attributed_campaign to {{steps.parse_utms.utm_campaign}}
  6. 6Map last_purchase_date to {{steps.trigger.event.created_at}}
What you should see: Open the contact in HubSpot and click 'View all properties' — the custom attribution fields should show the updated values from this order within 10-15 seconds of the workflow running.
Common mistake — HubSpot contact properties must be created manually in HubSpot Settings > Properties before you can write to them via API. If a property doesn't exist, the API call returns a 400 error with a message like 'PROPERTY_DOESNT_EXIST' — it will not auto-create the property.
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
7

Workflow Canvas > + Add a step > Shopify > Update Order

Write Shopify order tags back for segmentation

Add a Shopify 'Update Order' step to tag the Shopify order with the HubSpot attribution data. This creates a bidirectional record — Shopify orders get tagged with campaign names like 'hs-campaign:spring-sale-2024' and 'hs-source:google-cpc', which lets your Shopify analytics and any downstream apps also see the attribution context. Use the Shopify order ID from the trigger payload and append new tags without overwriting existing ones.

  1. 1Click '+ Add a step' and select Shopify
  2. 2Choose 'Update Order'
  3. 3Set Order ID to {{steps.trigger.event.id}}
  4. 4Set Tags to a concatenated string of existing tags plus your new attribution tags
  5. 5Click 'Test' and verify the tags appear in Shopify admin under the order
What you should see: Open the order in Shopify Admin > Orders > [Order Number] — the Tags field at the bottom right should show the new campaign attribution tags alongside any pre-existing tags.
Common mistake — Shopify's Update Order endpoint replaces the entire tags field — it does not append. If you pass only your new tags, all existing tags on the order are deleted. Your code must first read the existing tags from the trigger payload and concatenate the new ones before writing.
8

Workflow Canvas > Step options (...) > Add error handler

Add error handling with a catch branch

Click the '...' menu on any step that might fail (the HubSpot contact lookup and both write steps are the highest-risk) and enable 'Continue on error' or add an error path. In Pipedream, you can add a conditional step after the contact lookup that checks if the contact was found before proceeding. Log failed attribution events to a Pipedream data store or send a summary to a Slack channel so unmatched orders don't silently drop attribution data.

  1. 1Click the '...' menu on the HubSpot 'Find Contact' step
  2. 2Enable 'Continue workflow on error'
  3. 3Add a conditional step below to check if steps.find_contact.contact exists
  4. 4In the false branch, add a Pipedream Data Store step to log the unmatched order
  5. 5Optionally add a Slack notification step in the false branch
What you should see: When a test order comes in with an email not in HubSpot, the workflow should complete without a red error state — the false branch should run instead, and you should see the order logged in your Pipedream Data Store or receive a Slack message.
9

Shopify Admin > Settings > Payments > Test mode

Test with a real Shopify test order

In Shopify Admin, go to Settings > Payments and enable test mode. Place a test order using an email address that exists in your HubSpot CRM. Watch the Pipedream workflow event inspector fire in real time — each step should go green within a few seconds. Confirm the deal was created in HubSpot, the contact properties were updated, and the Shopify order now has attribution tags. Then disable Shopify test mode.

  1. 1Enable test mode in Shopify Payments settings
  2. 2Place a test order using a real HubSpot contact email
  3. 3Switch to Pipedream and watch the workflow run in the event inspector
  4. 4Verify each step shows green status
  5. 5Open HubSpot CRM and confirm the deal and contact property updates
  6. 6Disable Shopify test mode when done
What you should see: All 7 steps in the workflow should show green checkmarks in Pipedream's event inspector. The HubSpot deal should appear under the test contact with the correct amount and stage. The Shopify test order should show attribution tags.
Common mistake — Shopify test orders created via the admin's 'Create order' button do not fire the orders/create webhook — only orders placed through the actual checkout flow trigger it. Use Shopify's Bogus Gateway in test mode and go through the real checkout URL.
Pipedream
▶ Deploy & test
executed
HubSpot
Shopify
Shopify
🔔 notification
received
10

Workflow Canvas > Deploy button (top right)

Deploy the workflow and monitor the first 48 hours

Click 'Deploy' in the top right of the Pipedream workflow canvas to move the workflow from development to production. Pipedream will activate the Shopify webhook and start processing live orders. Monitor the Pipedream event inspector for the first 48 hours — check for any steps showing yellow warnings or red errors. Pay particular attention to orders where the HubSpot contact lookup returns empty, as these are your unmatched attribution gaps.

  1. 1Click the blue 'Deploy' button in the top right corner
  2. 2Confirm deployment in the dialog that appears
  3. 3Click 'Runs' in the left sidebar to monitor live events
  4. 4Filter by 'Error' status to catch any failing runs immediately
  5. 5Check your Pipedream Data Store for unmatched order logs after 24 hours
What you should see: The workflow status badge should change from 'Development' to 'Deployed' (shown in green). Incoming Shopify orders should appear in the Runs tab within seconds of being placed, with each run showing the full step-by-step execution log.
Common mistake — Pipedream's free tier allows 10,000 invocations/month but limits workflow execution time to 30 seconds per run. This workflow typically completes in 3-8 seconds, so you're fine — but if you add more API steps later, watch the execution time counter in the run log.

Paste this code into the Node.js step at position 2 (immediately after the Shopify trigger). It parses UTM parameters from Shopify's landing_site URL, falls back gracefully to 'direct' when landing_site is null, converts the order total to a proper float, and exports a clean attribution object that every downstream HubSpot step can reference via steps.parse_attribution.

JavaScript — Code Step// Step: parse_attribution
▸ Show code
// Step: parse_attribution
// Extracts UTM data from Shopify order and prepares HubSpot-ready attribution fields
export default defineComponent({

... expand to see full code

// Step: parse_attribution
// Extracts UTM data from Shopify order and prepares HubSpot-ready attribution fields

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

    // Parse UTM parameters from landing_site URL
    let utmSource = 'direct';
    let utmMedium = null;
    let utmCampaign = 'direct';
    let utmContent = null;

    if (order.landing_site) {
      try {
        const url = new URL(order.landing_site);
        utmSource = url.searchParams.get('utm_source') || 'direct';
        utmMedium = url.searchParams.get('utm_medium') || null;
        utmCampaign = url.searchParams.get('utm_campaign') || 'direct';
        utmContent = url.searchParams.get('utm_content') || null;
      } catch (e) {
        // landing_site exists but isn't a valid URL — treat as direct
        console.log(`Could not parse landing_site: ${order.landing_site}`);
      }
    }

    // Safely convert Shopify's string price to a float HubSpot will accept
    const orderRevenue = parseFloat(order.total_price);
    if (isNaN(orderRevenue)) {
      throw new Error(`Invalid total_price value: ${order.total_price}`);
    }

    // Build clean product name list from line items
    const productNames = (order.line_items || [])
      .map(item => `${item.title} (x${item.quantity})`)
      .join(', ');

    // ISO date string for HubSpot date properties (milliseconds since epoch)
    const closeDateMs = new Date(order.created_at).getTime();

    return {
      customerEmail: order.customer?.email?.toLowerCase().trim(),
      shopifyOrderId: order.id.toString(),
      orderName: order.name,
      orderRevenue,
      closeDate: closeDateMs,
      utmSource,
      utmMedium,
      utmCampaign,
      utmContent,
      productNames,
      isGuestCheckout: !order.customer?.id,
    };
  }
});

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 has a developer (or you're comfortable writing Node.js) and you need custom attribution logic that no point-and-click tool can handle. Specifically: UTM parsing from Shopify's landing_site field, fallback attribution strategies for null traffic sources, or multi-touch weighting across order history. Pipedream fires the webhook in under 500ms of the Shopify order creation and gives you a Node.js environment to transform data exactly how you need it. The one scenario where you'd pick something else: if your attribution needs are simple last-touch UTM → HubSpot deal, HubSpot's native Shopify integration (under Settings > Integrations > Shopify) handles that with zero code and zero monthly cost.

Cost

Pipedream's free tier gives you 10,000 invocations/month. Each order run through this workflow costs 1 invocation plus roughly 2-3 compute credits per step — at 5 active steps, that's about 15 credits per order. The free tier includes 100,000 compute credits/month, so you can process roughly 6,600 orders/month before hitting limits. Stores doing 300+ orders/day need Pipedream's Basic plan at $19/month (10M credits). Compare that to Zapier, where this same 5-step workflow would cost you $49-$73/month on a Professional plan at equivalent volume. Make's Core plan at $9/month handles the same volume for significantly less, but you lose the Node.js code step flexibility.

Tradeoffs

Zapier has a pre-built 'HubSpot + Shopify' template that maps order data to contacts in about 8 minutes — if your needs fit the template, it wins on setup speed. Make's scenario builder is better for conditional routing (e.g. apply different attribution logic for orders over $500 vs. under $500) because its filter UI is visual and immediate. n8n gives you the same Node.js power as Pipedream and is cheaper at self-hosted scale, but the webhook setup is more manual — Pipedream auto-registers Shopify webhooks through its Connected Accounts system, saving 20-30 minutes per setup. Power Automate has a Shopify connector but it's a premium connector ($15/user/month add-on) and HubSpot's PA connector is notoriously slow to authenticate — avoid it for this use case. Pipedream wins specifically because of how fast the webhook processing is and how cleanly the Connected Accounts system handles Shopify + HubSpot auth simultaneously.

Three things you'll hit after setup. First: Shopify's landing_site field is null for roughly 30-40% of orders from returning customers who navigate directly — your attribution reports will show a lot of 'direct' unless you implement a secondary attribution method like HubSpot's tracking cookie or a first-touch property stored on the contact. Second: HubSpot's API rate limit is 100 requests per 10 seconds on Starter plans. During a flash sale where you process 50 orders in a minute, Pipedream will queue the webhooks but your HubSpot write steps will start returning 429 rate limit errors — add exponential backoff retry logic in your code step if you run promotions. Third: Shopify returns created_at timestamps in the store's local timezone, but HubSpot's date properties expect Unix milliseconds in UTC. If you don't convert properly, your deal close dates will be off by your timezone offset hours and your monthly revenue attribution reports in HubSpot will miscount orders near midnight.

Ideas for what to build next

  • Add campaign-level revenue roll-ups to HubSpotExtend this workflow to also write to HubSpot's custom object layer — create a 'Campaign Revenue' custom object and update an aggregate revenue total per campaign each time an order fires. This gives you a live campaign revenue dashboard inside HubSpot without needing to pull a deal report.
  • Build a weekly attribution digest to SlackCreate a second Pipedream workflow on a weekly schedule that queries HubSpot's deal API for the past 7 days, groups deals by utm_campaign, computes revenue per campaign, and posts a ranked summary to your #marketing Slack channel every Monday morning.
  • Add multi-touch attribution scoringModify the contact update step to maintain an array of all campaign touchpoints in a HubSpot multi-line text property, not just the last-touch campaign. Then add a code step that computes linear or time-decay attribution weights across touchpoints and writes individual campaign credit values back to HubSpot.

Related guides

Was this guide helpful?
HubSpot + Shopify overviewPipedream profile →