How to Sync Customer Data Between Shopify and Klaviyo

Keep your Shopify customer list in sync with Klaviyo for targeted marketing campaigns.

What This Integration Does

Klaviyo's segmentation only works well when customer profiles are accurate, current, and decorated with the right purchase signals. Shopify is the source of truth for who bought what, but on its own it can't drive flows on lifecycle stage, total spend, or last-product-purchased. This workflow pushes Shopify customers and the properties Klaviyo needs into Klaviyo profiles so your flows fire on real, up-to-date data.

It can run on a schedule (sweep recently-updated customers) or on a Shopify webhook (per-customer real-time updates). On each run it upserts a Klaviyo profile keyed by email, sets custom properties like total orders and lifetime value, and optionally adds the profile to a target list.

Prerequisites

  • A Shopify connection with read_customers and read_orders scopes.
  • A Klaviyo connection with a private API key that allows profile create/update and list membership.
  • The Klaviyo list id you want new profiles dropped into (if any).

Step 1: Trigger

Choose one of two trigger styles. For real-time sync use a Webhook trigger and configure a Shopify webhook for customers/create and customers/update. For bulk back-fills or hourly sweeps use a Schedule trigger and capture a since timestamp.

Step 2: Fetch Customers from Shopify

On the schedule path, add a Connector node calling shopify list-customers with updated_at_min={{ since }}. On the webhook path, take the customer id from the trigger payload and call get-customer for the full record. Either way you end up with a customer object to process.

Step 3: Enrich with Order Stats

To populate Klaviyo segments on purchase behaviour, call shopify list-orders filtered by customer_id={{ customer.id }}. Pipe the result into a Transform node that computes:

  • totalOrders - count of completed orders.
  • lifetimeValue - sum of total_price.
  • lastOrderAt - max processed_at.
  • firstOrderAt - min processed_at.

Step 4: Upsert the Klaviyo Profile

Add a Connector node calling klaviyo create-profile (Klaviyo treats this as upsert on email). Pass:

{
  "email": "{{ customer.email }}",
  "first_name": "{{ customer.first_name }}",
  "last_name": "{{ customer.last_name }}",
  "phone_number": "{{ customer.phone }}",
  "properties": {
    "shopify_id": "{{ customer.id }}",
    "total_orders": {{ totalOrders }},
    "lifetime_value": {{ lifetimeValue }},
    "last_order_at": "{{ lastOrderAt }}",
    "first_order_at": "{{ firstOrderAt }}",
    "accepts_marketing": {{ customer.accepts_marketing }}
  }
}

If you need to set properties on a profile that already exists and you have its id, use update-profile instead.

Step 5: Add the Profile to a List

Add a Condition node on customer.accepts_marketing. On true, call klaviyo add-profiles-to-list with the target list id and the email. Skip silently for customers who haven't opted in.

Step 6: Handle Failures

Wrap the Klaviyo calls in a Condition on response status. On failure, post to slack send-message with the Shopify customer id and Klaviyo error so a human can reconcile. Spojit's per-node retry handles transient 429s automatically.

Tips

  • Klaviyo's API is rate-limited to 75 requests per second on most endpoints. The add-profiles-to-list tool accepts batches - prefer batching over one-at-a-time when sweeping.
  • Stamp accepts_marketing as a Klaviyo property as well as gating list membership - this lets you re-derive segments without re-syncing.
  • If you sync large historical batches, run the workflow off-hours to avoid contention with Klaviyo flow sends.

Common Pitfalls

  • Email casing - Klaviyo treats profiles as case-sensitive in some legacy code paths. Normalise to lowercase before sending.
  • Missing emails - Shopify allows phone-only customers. Skip those (no email, no Klaviyo profile) rather than failing the run.
  • Unsubscribes - Re-adding a profile to a list will not re-subscribe an unsubscribed user. Check subscription_status before forcing list adds.
  • Custom property name churn - Once a property name is set in Klaviyo it sticks forever. Decide on snake_case vs camelCase once.

Testing

Pick one test customer in Shopify, run the workflow manually, then open the corresponding profile in Klaviyo. Confirm every property is populated and the list membership matches the accepts_marketing flag. Make a small change to the Shopify customer, re-trigger, and verify the Klaviyo profile updates in place rather than duplicating.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.