Connect Shopify with HubSpot

Implementation Guide

Overview: Connecting Shopify and HubSpot

For e-commerce and direct-to-consumer businesses that use HubSpot as their CRM and marketing platform, the Shopify-to-HubSpot integration is the critical data pipe that transforms transactional purchase events into relationship intelligence. Shopify captures the transactional truth—who bought what, when, for how much, using which channel—but it lacks the marketing automation depth to convert that purchase history into personalized post-purchase communication, retention campaigns, or predictive churn signals. HubSpot provides exactly that marketing automation depth but has no visibility into purchase behavior unless Shopify data flows into it continuously and accurately.

This integration enables a complete customer lifecycle view within HubSpot: a contact's record shows not only their email engagement and landing page visits but also their full order history, average order value, product categories purchased, fulfillment status, and lifetime value. With this enriched profile, HubSpot workflows can trigger post-purchase onboarding sequences, re-engagement campaigns based on time since last purchase, upsell sequences based on product affinity, and win-back automations for customers who abandon carts. The business impact is measurable: automated abandoned cart recovery alone typically recovers 5-15% of otherwise lost revenue when properly instrumented.

Core Prerequisites

On the Shopify side, you must create a Custom App within your Shopify Admin under Settings > Apps and Sales Channels > Develop Apps. The custom app requires the following Admin API access scopes: read_orders and write_orders for order event access, read_customers and write_customers for customer record synchronization, read_products for product catalog data needed to enrich line items with product metadata, and read_checkouts for abandoned checkout event access. After configuring the app's scopes, install the app to your store and retain the Admin API access token. Separately, navigate to Settings > Notifications > Webhooks (or manage webhooks programmatically via the Shopify Admin API at POST /admin/api/2024-01/webhooks.json) to register webhook subscriptions for the following topics: orders/create, orders/fulfilled, customers/create, customers/update, and checkouts/create (for abandoned checkout tracking, as Shopify fires checkouts/create when a checkout is initiated but not yet completed, and you detect abandonment by the absence of a subsequent orders/paid event within a defined window). Every Shopify webhook request includes an X-Shopify-Hmac-SHA256 header containing a base64-encoded HMAC-SHA256 signature computed from the raw request body using your app's client secret. Your endpoint must verify this signature on every request before processing.

On the HubSpot side, create a Private App with the following scopes: crm.objects.contacts.read and crm.objects.contacts.write for contact management, crm.objects.deals.read and crm.objects.deals.write for order-to-deal pipeline management, crm.objects.companies.read and crm.objects.companies.write if you are mapping Shopify's B2B company data to HubSpot Companies, and crm.schemas.contacts.read to introspect custom contact properties. Before running the integration, create a set of custom contact properties in HubSpot to store Shopify-specific data: shopify_customer_id (single-line text, used as external ID), total_order_count (number), total_lifetime_value (number), last_order_date (date), last_order_id (single-line text), average_order_value (number), and preferred_product_category (single-line text or dropdown enumeration). These properties enable segmentation and workflow enrollment in HubSpot based on e-commerce behavioral attributes.

Top Enterprise Use Cases

The primary use case is automated contact creation and enrichment from new Shopify customers. When Shopify fires a customers/create event, the integration creates a corresponding HubSpot Contact record (or updates an existing one matched on email address) with the customer's full name, phone, billing address, and initial purchase context. Setting the lifecyclestage to customer at creation immediately excludes this contact from lead-nurture workflows and routes them into post-purchase sequences instead.

Abandoned checkout recovery is arguably the highest-ROI use case for most e-commerce companies. When Shopify fires a checkouts/create event and no corresponding orders/paid event arrives within a configured window (typically 60 minutes, handled by a delayed job scheduler in your middleware), the integration creates or updates a HubSpot Contact with the abandoned cart value and the checkout URL, then enrolls them in a HubSpot Workflow configured to deliver a series of recovery emails. The Shopify checkout object provides abandoned_checkout_url—a direct link that re-hydrates the customer's cart—which should be stored as a HubSpot contact property and used as a dynamic token in the recovery email template.

Order-to-deal pipeline synchronization is critical for businesses that use HubSpot for B2B account management. Each Shopify order above a defined threshold (e.g., orders over $5,000) can create a HubSpot Deal in a dedicated "Customer Orders" pipeline, with the deal value, close date, and associated company allowing account managers to track large purchase events within their CRM view without leaving HubSpot.

Post-purchase lifecycle automation based on customer LTV segmentation is a fourth high-value use case. After each order, the integration recalculates total_lifetime_value and total_order_count for the customer and updates their HubSpot contact properties. HubSpot Workflows triggered by these property changes can automatically promote contacts between segments: from "First-Time Buyer" to "Repeat Customer" to "VIP" based on defined thresholds, enrolling each segment in the appropriate nurture track and promotional campaign sequences.

Step-by-Step Implementation Guide

When your Shopify webhook endpoint receives an orders/create POST request, after HMAC signature verification, you will have access to the full order object. A simplified but representative payload structure is:

{
  "id": 4201456789,
  "order_number": 1025,
  "email": "[email protected]",
  "created_at": "2025-09-15T14:22:11-07:00",
  "total_price": "149.95",
  "subtotal_price": "129.95",
  "total_tax": "12.00",
  "financial_status": "paid",
  "fulfillment_status": null,
  "customer": {
    "id": 7890123456,
    "email": "[email protected]",
    "first_name": "Alex",
    "last_name": "Morgan",
    "orders_count": 3,
    "total_spent": "349.85"
  },
  "line_items": [
    {
      "product_id": 1122334455,
      "title": "Premium Subscription Box",
      "quantity": 1,
      "price": "129.95"
    }
  ],
  "billing_address": {
    "city": "Austin",
    "province": "Texas",
    "country": "United States"
  }
}

Your middleware extracts the customer email and calls HubSpot's CRM search endpoint to find an existing contact: POST https://api.hubapi.com/crm/v3/objects/contacts/search with a filter group matching email equals [email protected]. If a contact is found, issue a PATCH to /crm/v3/objects/contacts/{contactId} to update the Shopify-specific custom properties. If no contact is found, issue a POST to /crm/v3/objects/contacts with the full contact payload. In both cases, the payload should include the standard HubSpot property names alongside your custom Shopify properties:

{
  "properties": {
    "email": "[email protected]",
    "firstname": "Alex",
    "lastname": "Morgan",
    "lifecyclestage": "customer",
    "shopify_customer_id": "7890123456",
    "total_order_count": 3,
    "total_lifetime_value": 349.85,
    "last_order_date": "2025-09-15",
    "last_order_id": "4201456789",
    "average_order_value": 116.62
  }
}

For Zapier, the Shopify "New Order" trigger fires on every new order and exposes the full order object as dynamic fields. A "Find Contact in HubSpot" step checks for an existing contact by email. Use a Paths step to branch: the "contact found" path uses "Update Contact Property in HubSpot" targeting the contact's internal ID returned by the Find step; the "contact not found" path uses "Create Contact in HubSpot." For the custom Shopify properties to appear in Zapier's HubSpot action field list, you must create them in HubSpot's contact property settings before configuring the Zap—Zapier dynamically fetches available properties from the HubSpot API at Zap configuration time.

For Make, the Shopify "Watch Events" module supports webhook-based triggering for order events. After connecting your Shopify store and authorizing the custom app credentials, select orders/create as the watched event. Make's HubSpot modules include "Search for Contacts," "Create a Contact," and "Update a Contact Property." Chain these in sequence using a Router for the create/update branch. For the abandoned checkout use case, Make's delay functionality (using the "Sleep" module with a 60-minute delay, or a more robust approach using a Make data store to track checkout IDs and a scheduled scenario to check for unconverted checkouts) handles the time-window detection. Store the checkout ID and timestamp in a Make Data Store on checkouts/create receipt; a second scheduled scenario queries the data store for checkouts older than 60 minutes that have no corresponding order ID flagged as resolved.

Common Pitfalls & Troubleshooting

A 422 Unprocessable Entity from HubSpot's Contact API most commonly occurs when you attempt to set a contact property to a value that does not exist in the property's defined option set (for dropdown or radio-select properties), or when you send a date value in the wrong format. HubSpot's date properties expect Unix millisecond timestamps for API writes—2025-09-15 in string format will return a 422; the correct value is 1726358400000. Convert all date strings from Shopify's ISO 8601 format to Unix milliseconds before passing them to HubSpot.

Shopify's webhook delivery uses a retry mechanism with exponential backoff, delivering up to 19 retry attempts over 48 hours for endpoints that return non-200 status codes. If your middleware takes longer than 5 seconds to process a webhook and returns a timeout, Shopify considers the delivery failed and will retry. Implement a fast acknowledgment pattern: immediately return HTTP 200 upon receiving the webhook, enqueue the payload to an async job processor (Sidekiq, Celery, BullMQ, etc.), and process the actual HubSpot API calls asynchronously. This prevents Shopify's 5-second timeout from triggering unnecessary retries while ensuring your business logic executes reliably.

Rate limiting on HubSpot's API manifests as 429 Too Many Requests. HubSpot's CRM API rate limits vary by portal tier: Free and Starter portals receive 100 requests per 10 seconds; Professional and Enterprise portals receive 150 requests per 10 seconds. During initial historical data imports—when you are backfilling years of Shopify customer and order history into HubSpot—you will almost certainly hit these limits. Implement a leaky-bucket rate limiter in your import job and process records in batches using HubSpot's Batch Create endpoints (POST /crm/v3/objects/contacts/batch/create), which accept up to 100 contact records per request, dramatically reducing total API call volume compared to individual contact creation calls.

Duplicate contact creation is a persistent problem in Shopify-to-HubSpot integrations, particularly when customers check out as guests using different email addresses or when the same customer places orders in rapid succession, causing concurrent webhook deliveries to both execute the "contact not found" branch simultaneously. Mitigate this by using HubSpot's upsert behavior wherever available, and by implementing a short-lived distributed lock (using Redis or a database-level advisory lock) keyed on the customer's email address for the duration of the contact creation transaction.