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

How to Recover Abandoned Carts with Pipedream, Shopify & HubSpot

Fires a HubSpot email sequence automatically when a Shopify customer abandons their cart, using Pipedream webhooks and Node.js to enroll or update the contact 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

Dev-comfortable e-commerce teams who need custom cart-abandonment logic — like skipping sequences for wholesale customers or adjusting delay windows by cart value.

Not ideal for

Shopify merchants who want abandoned cart emails without writing any code — use Klaviyo's native Shopify integration or Zapier instead.

Sync type

real-time

Use case type

notification

Real-World Example

💡

A 12-person DTC apparel brand uses this to enroll customers in a three-email HubSpot sequence the moment Shopify fires a cart abandonment webhook. Before this, the marketing team manually exported abandoned cart CSVs every morning and bulk-enrolled contacts — a 45-minute daily task that meant some customers got emails 18+ hours after abandoning. Now the first email goes out within 4 minutes of abandonment.

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.

Shopify store with a custom app or Partner app that has the read_checkouts and read_orders API scopes enabled
HubSpot account on Sales Hub Starter or above — required for the Sequences API enrollment endpoint
HubSpot connected account added in Pipedream with scopes: crm.objects.contacts.read, crm.objects.contacts.write, and sales-email-read
Custom HubSpot contact properties created before running the workflow: abandoned_cart_value (number), abandoned_checkout_url (single-line text), abandoned_cart_item_count (number)
The numeric HubSpot Sequence ID and the HubSpot sender userId for the account that will send the sequence emails

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Customer Emailemail
First Namefirstname
Abandoned Cart Valueabandoned_cart_value
Abandoned Checkout URLabandoned_checkout_url
5 optional fields▸ show
Last Namelastname
Abandoned Cart Item Countabandoned_cart_item_count
Cart Currencyabandoned_cart_currency
Checkout Created Atabandoned_cart_date
Phone Numberphone

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-right corner of the Workflows dashboard. Give the workflow a name like 'Shopify → HubSpot Abandoned Cart'. You'll land on a blank canvas with a trigger slot at the top and an empty steps area below. Pipedream generates a unique HTTPS endpoint URL the moment you add an HTTP trigger — you'll use this URL in Shopify next.

  1. 1Click 'New Workflow' in the top-right of the Workflows dashboard
  2. 2Enter a workflow name: 'Shopify → HubSpot Abandoned Cart'
  3. 3Click the trigger slot labeled 'Add a trigger'
  4. 4Search for 'HTTP / Webhook' and select it
  5. 5Set the response type to 'Return a custom response' so Shopify's webhook delivery can receive a 200 OK
What you should see: You should see a unique webhook URL like https://eo1abc23def.m.pipedream.net displayed under the trigger step. Copy this URL — you need it in Step 2.
Common mistake — Pipedream generates a new URL each time you delete and recreate the trigger. If you ever replace this trigger step, update the Shopify webhook immediately or deliveries will silently fail.
2

Shopify Admin > Settings > Notifications > Webhooks

Register the webhook in Shopify

In your Shopify admin, navigate to Settings > Notifications > Webhooks. Shopify natively supports an 'Abandoned checkouts / Checkout created' webhook event — this fires when a checkout is created but not completed after the customer has entered their email. Paste your Pipedream URL into the URL field, set the format to JSON, and save. Shopify will send a test ping to confirm the endpoint is reachable.

  1. 1Open Shopify Admin and go to Settings > Notifications
  2. 2Scroll to the bottom and click 'Create webhook'
  3. 3Set 'Event' to 'Checkout created'
  4. 4Paste your Pipedream webhook URL into the URL field
  5. 5Set format to JSON and click 'Save webhook'
What you should see: Shopify shows a green checkmark and sends a test payload to Pipedream. In your Pipedream workflow, click 'Run once' — the trigger step should show 'Event received' with a raw JSON preview of a checkout object.
Common mistake — Shopify fires 'Checkout created' as soon as a customer reaches the checkout page and enters their email — not after a delay. You must build the abandonment delay logic in Pipedream (Step 5), otherwise you'll enroll customers who are mid-purchase.
3

Workflow Canvas > + Add Step > Run Node.js code

Validate the incoming Shopify payload

Add a Node.js code step immediately after the trigger to validate the webhook and confirm the checkout is genuinely abandoned. Shopify does not distinguish between an active checkout and an abandoned one in the webhook event name — that determination happens based on whether an order was completed. At this stage, extract the fields you need: customer email, first name, cart total, and the abandoned checkout URL. Log these to confirm the data shape before building downstream logic.

  1. 1Click '+ Add Step' below the trigger
  2. 2Select 'Run Node.js code'
  3. 3Paste the extraction code (see Pro Tip below)
  4. 4Click 'Test' to run the step against the Shopify test payload
What you should see: The step output panel shows a structured object with email, firstName, cartTotal, checkoutUrl, and abandonedAt timestamp. No errors in the logs panel.
4

Workflow Canvas > + Add Step > Delay

Add a delay to confirm abandonment

Shopify's webhook fires the moment a checkout is created, not after a proven abandonment window. Use Pipedream's built-in 'Delay' step to pause execution for 60 minutes. If the customer completes their purchase within that window, you'll want to cancel the enrollment — but Pipedream's standard delay doesn't support cancellation. The workaround: after the delay, query Shopify's Orders API to check whether an order exists for this checkout ID before proceeding.

  1. 1Click '+ Add Step' and search for 'Delay'
  2. 2Select the Pipedream 'Delay' built-in action
  3. 3Set the delay duration to 3600 seconds (60 minutes)
  4. 4Click 'Save and continue'
What you should see: The step shows 'Paused for 1 hour' in the execution log. Pipedream resumes execution automatically after the delay without any additional configuration.
Common mistake — Pipedream's free tier does not support delays longer than 24 hours. The paid 'Advanced' plan supports delays up to 1 year. If your abandonment strategy uses 24-hour or 48-hour delays, confirm your plan before building.
5

Workflow Canvas > + Add Step > Run Node.js code

Check Shopify Orders API to confirm no purchase

After the delay, add a Node.js code step that queries the Shopify Admin REST API to check whether an order was placed against this checkout token. Use the GET /admin/api/2024-01/orders.json?status=any endpoint and filter by the checkout token from the original payload. If an order exists, set a flag to skip HubSpot enrollment and exit the workflow cleanly using $.flow.exit().

  1. 1Click '+ Add Step' and select 'Run Node.js code'
  2. 2Connect your Shopify store credentials via the Connected Accounts panel on the right
  3. 3Write the Orders API check using the checkout token from step 3's output
  4. 4Call $.flow.exit('Order completed — skipping enrollment') if an order is found
What you should see: If a purchase was completed, the execution log shows 'Order completed — skipping enrollment' and the workflow stops. If no order exists, execution continues to the next step.
Common mistake — Shopify's Orders API requires the read_orders scope. If your connected app was created without this scope, the API returns a 403 and the workflow crashes silently — add the scope in Shopify Partners and reconnect the account.
6

Workflow Canvas > + Add Step > HubSpot > Search Contacts

Look up or create the HubSpot contact

Add the HubSpot app step to search for an existing contact by email. In Pipedream's step library, search for 'HubSpot' and choose 'Search Contacts'. Use the email address extracted in Step 3 as the search value. If the contact exists, retrieve their contact ID for the enrollment step. If no contact is found, add a second HubSpot step to create a new contact with the cart data before enrolling them.

  1. 1Click '+ Add Step' and search for 'HubSpot'
  2. 2Select 'Search Contacts' from the action list
  3. 3Connect your HubSpot account via Connected Accounts
  4. 4Set the search filter: property = 'email', value = steps.validate_payload.$return_value.email
  5. 5Add a conditional Node.js step: if contact not found, run 'Create Contact' HubSpot action before proceeding
What you should see: The step returns either a contact object with a numeric HubSpot contact ID, or an empty results array. The next conditional step branches accordingly.
Common mistake — HubSpot's contact search API returns partial matches if the email domain is common. Always match on the exact email property, not a full-text search, or you'll enroll the wrong contact.
7

Workflow Canvas > + Add Step > HubSpot > Update Contact

Update HubSpot contact properties with cart data

Before enrolling the contact in a sequence, update their HubSpot record with the abandoned cart details. Add a HubSpot 'Update Contact' step. Map the Shopify cart total to a custom HubSpot property (e.g., abandoned_cart_value), the checkout URL to abandoned_checkout_url, and the cart item count to abandoned_cart_item_count. These properties make the sequence emails personalizable with tokens like {{contact.abandoned_cart_value}}.

  1. 1Click '+ Add Step' and select HubSpot > Update Contact
  2. 2Set Contact ID to the ID returned in Step 6
  3. 3Add property: abandoned_cart_value = steps.validate_payload.$return_value.cartTotal
  4. 4Add property: abandoned_checkout_url = steps.validate_payload.$return_value.checkoutUrl
  5. 5Add property: abandoned_cart_item_count = steps.validate_payload.$return_value.itemCount
What you should see: In HubSpot CRM, open the contact record for the test email. The custom properties section should show the cart value, checkout URL, and item count populated with values from the Shopify test payload.
Common mistake — Custom HubSpot properties must be created manually in HubSpot before Pipedream can write to them. Go to HubSpot > Settings > Properties and create each field as a 'Contact' property first, or the API call returns a 400 error.
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
8

Workflow Canvas > + Add Step > Run Node.js code

Enroll the contact in a HubSpot sequence

HubSpot's Sequences API allows programmatic enrollment via a POST to /crm/v3/objects/contacts/{contactId}/associations or via the Engagements API. Pipedream does not have a pre-built 'Enroll in Sequence' action, so you'll write a Node.js code step that calls HubSpot's Sequence Enrollment endpoint directly. You need the sequence ID (find it in HubSpot > Sequences > select your sequence > the ID is in the URL) and the sender's userId to assign email ownership.

  1. 1Click '+ Add Step' and select 'Run Node.js code'
  2. 2Use the HubSpot connected account's OAuth token via auths.hubspot.oauth_access_token
  3. 3POST to https://api.hubapi.com/automation/v4/sequences/enrollments
  4. 4Set body: { contactId, sequenceId, senderId } where sequenceId is the numeric ID from the HubSpot Sequences URL
  5. 5Log the response status and enrollment ID for debugging
What you should see: The API returns a 201 with an enrollment object. In HubSpot, open the contact record and click the 'Sequences' tab — you should see the contact listed as 'Active' in your chosen sequence.
Common mistake — HubSpot's Sequences API only works with Sales Hub Starter or above. Marketing Hub contacts without a Sales Hub seat cannot be enrolled via API. Check your HubSpot subscription before testing.
9

Workflow Canvas > Trigger Step > Response Settings

Return a 200 response to Shopify

Shopify expects a 200 OK response within 5 seconds of delivering a webhook. Because Pipedream's delay step pauses execution for 60 minutes, the initial webhook response must be sent immediately — before the delay. Go back to your HTTP trigger step and confirm 'Return a custom response' is enabled. Add a $.respond() call at the very beginning of your first code step to send the 200 immediately, decoupling the response from the downstream workflow execution.

  1. 1Click the HTTP trigger step at the top of the workflow
  2. 2Confirm 'Return a custom response' is toggled ON
  3. 3In your Step 3 Node.js code, add $.respond({ status: 200, body: 'received' }) as the first line of execution
  4. 4Click 'Deploy' to save the workflow
What you should see: In Shopify's webhook log (Settings > Notifications > Webhooks > Recent deliveries), the delivery shows a 200 response code with no timeout errors.
10

pipedream.com > Workflows > [Your Workflow] > Executions

Test end-to-end with a real abandoned checkout

In Shopify, create a test checkout by adding a product to a cart, entering a real email address at the checkout page, and then closing the tab without completing the purchase. In Pipedream, open the workflow's execution log and watch the event appear. After 60 minutes, confirm the workflow resumes, the Shopify Orders check passes, the HubSpot contact is updated, and the sequence enrollment fires. Check HubSpot for the contact's sequence status.

  1. 1Open your Shopify store, add a product to cart, begin checkout, enter a test email, then abandon the tab
  2. 2In Pipedream, click the 'Executions' tab on your workflow
  3. 3Watch for a new execution row to appear within 30 seconds
  4. 4After 60 minutes, confirm the execution resumes and all downstream steps show green checkmarks
  5. 5Open HubSpot and verify the contact record has updated cart properties and shows active sequence enrollment
What you should see: Every step in the Pipedream execution log shows a green checkmark. The HubSpot contact record shows abandoned_cart_value, abandoned_checkout_url, and an active sequence enrollment with the correct sender.

Paste this code into the Node.js validation step (Step 3) and again into the Shopify Orders check step (Step 5). The first function extracts and normalizes cart data from the Shopify webhook. The second queries the Orders API and exits the workflow cleanly if a purchase was completed. Both use Pipedream's $.flow.exit() and the connected Shopify account token available via auths.shopify.

JavaScript — Code Step// Step 3: Extract and normalize Shopify abandoned cart data
▸ Show code
// Step 3: Extract and normalize Shopify abandoned cart data
export default defineComponent({
  async run({ steps, $ }) {

... expand to see full code

// Step 3: Extract and normalize Shopify abandoned cart data
export default defineComponent({
  async run({ steps, $ }) {
    const body = steps.trigger.event.body;

    // Immediately acknowledge the webhook so Shopify doesn't retry
    await $.respond({ status: 200, body: 'received' });

    const email = (body.email || '').toLowerCase().trim();
    if (!email) {
      await $.flow.exit('No email on checkout — skipping');
    }

    const cartData = {
      checkoutId:    body.id,
      checkoutToken: body.token,
      email,
      firstName:     body.billing_address?.first_name || body.shipping_address?.first_name || '',
      lastName:      body.billing_address?.last_name  || body.shipping_address?.last_name  || '',
      cartTotal:     parseFloat(body.total_price || '0').toFixed(2),
      currency:      body.currency || 'USD',
      checkoutUrl:   body.abandoned_checkout_url,
      itemCount:     (body.line_items || []).length,
      createdAt:     body.created_at,
    };

    console.log('Cart data extracted:', JSON.stringify(cartData, null, 2));
    return cartData;
  },
});

// Step 5: Check Shopify Orders API — skip if purchase completed
export default defineComponent({
  props: {
    shopify: {
      type: 'app',
      app: 'shopify',
    },
  },
  async run({ steps, $ }) {
    const { checkoutToken, email } = steps.validate_payload.$return_value;

    const shop   = this.shopify.$auth.shop_id;
    const token  = this.shopify.$auth.oauth_access_token;
    const url    = `https://${shop}.myshopify.com/admin/api/2024-01/orders.json` +
                   `?checkout_token=${checkoutToken}&status=any&limit=1`;

    const response = await fetch(url, {
      headers: {
        'X-Shopify-Access-Token': token,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      throw new Error(`Shopify Orders API error: ${response.status} ${await response.text()}`);
    }

    const { orders } = await response.json();

    if (orders && orders.length > 0) {
      console.log(`Order found for checkout token ${checkoutToken} — purchase completed, exiting.`);
      await $.flow.exit('Purchase completed — skipping HubSpot enrollment');
    }

    console.log(`No order found for ${email} — proceeding to HubSpot enrollment.`);
    return { proceed: true, email };
  },
});
Pipedream
▶ Deploy & test
executed
HubSpot
Shopify
Shopify
🔔 notification
received

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 includes at least one developer and you need custom abandonment logic — like skipping enrollment for wholesale customers, branching sequences by cart value, or checking a second data source before firing HubSpot. Pipedream's instant webhook processing means the checkout event hits your workflow in under a second, and Node.js code steps let you call the Shopify Orders API mid-execution to verify a purchase hasn't been completed. If nobody on your team writes code, use Klaviyo instead — it has native Shopify abandoned cart triggers with zero setup and handles the abandonment window automatically.

Cost

Pipedream's pricing is credit-based. This workflow consumes roughly 4-6 credits per execution: one for the trigger, one for the delay, one for the Shopify Orders API call, one for the HubSpot contact lookup, one for the contact update, and one for the sequence enrollment. At 500 abandoned carts per month, you're burning approximately 2,500-3,000 credits monthly. Pipedream's free tier includes 2,500 credits/month — you'll hit that ceiling fast. The Basic plan at $29/month includes 20,000 credits, which covers roughly 3,300 carts/month comfortably. Make.com handles the same volume for $9/month on its Core plan with no credit counting. Zapier would cost $49-$73/month for the same volume on a Professional plan.

Tradeoffs

Zapier has a cleaner UI for setting up the HubSpot sequence enrollment step and handles the Shopify trigger with zero code — setup takes 15 minutes versus 2-3 hours on Pipedream. But Zapier can't do the mid-execution Shopify Orders check without a separate Zap and a Zapier Storage lookup, which is fragile. Make.com has a native 'Sleep' module that handles the abandonment delay elegantly and its HTTP module calls the Shopify Orders API cleanly — but Make has a known latency issue where webhook processing can take 30-90 seconds on the free tier, which matters if you're competing for customer attention. n8n gives you the same Node.js flexibility as Pipedream for free if you self-host, but you're managing infrastructure. Power Automate doesn't have a native Shopify connector — you'd use the HTTP action for every Shopify call, which makes the workflow brittle. Pipedream is the right call here specifically because the Orders API check mid-execution is hard to replicate cleanly anywhere else.

Three things you'll hit after launch. First: Shopify fires 'Checkout created' for every checkout, including ones where the customer eventually completes the purchase during your delay window — your Orders API check is the only thing preventing enrolled paying customers from getting 'you forgot something' emails. Test this case explicitly before going live. Second: HubSpot rate-limits the Sequences enrollment API to 10 requests per second and 1,000 per day on Sales Hub Starter. At high cart abandonment volume, you can hit the daily cap — monitor the HubSpot API usage dashboard and upgrade to Sales Hub Professional if needed. Third: Shopify's abandoned_checkout_url expires after 24 hours if not recovered. If your sequence sends a recovery email more than 24 hours after abandonment, the link in the email returns a 404. Either keep your first sequence email within the 24-hour window or regenerate the URL via Shopify's API before sending.

Ideas for what to build next

  • Add cart value branchingSplit the workflow at Step 8 to enroll high-value carts (>$150) in a premium sequence with a discount offer, and low-value carts in a simpler 2-email reminder sequence. Add a Pipedream 'Filter' step that checks steps.validate_payload.$return_value.cartTotal and routes to the correct HubSpot sequence ID.
  • Sync sequence reply data back to ShopifyAdd a second Pipedream workflow triggered by HubSpot's 'Sequence Enrolled Contact Replied' webhook. When a customer replies to the recovery email, update a Shopify customer metafield to flag them as 'email engaged' — useful for excluding them from Shopify-native discount campaigns.
  • Add Slack alerting for high-value abandonmentsAfter the HubSpot enrollment step, add a Slack step that posts to your #ecommerce channel whenever a cart over $500 is abandoned. Include the customer name, cart total, and the HubSpot contact URL so your team can do manual outreach alongside the automated sequence.

Related guides

Was this guide helpful?
HubSpot + Shopify overviewPipedream profile →