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_customersandread_ordersscopes. - 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 oftotal_price.lastOrderAt- maxprocessed_at.firstOrderAt- minprocessed_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-listtool accepts batches - prefer batching over one-at-a-time when sweeping. - Stamp
accepts_marketingas 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_statusbefore 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.