Home Blog Contact
Home/Blog/How to Find and Merge Duplicate Shopify Custo…
How toEcommerceShopifyKlaviyoData Hygiene

How to Find and Merge Duplicate Shopify Customers Safely

7 min readBy Miloš Mitrović

Duplicate Shopify customer records are the quiet reason your lifecycle reports never quite reconcile. When one shopper exists as two or three customer objects, their orders split across those objects, so a repeat buyer reads as several one-time buyers and every downstream count in Klaviyo or HubSpot inherits the error. The short answer: normalize identifiers, cluster the collisions, preview each pair before you touch it, then merge in Shopify first and reconcile the destination platform second, because the two systems do not dedupe on the same key.

Key takeaways

  • The fix in one line: group customers by lowercased email and E.164 phone, confirm each pair with customerMergePreview, merge with the native tool or the customerMerge mutation, then reconcile Klaviyo or HubSpot.
  • Duplicates come from guest checkout followed by account creation, mixed-case or aliased emails, and phone-only orders that never carry an email.
  • Shopify merges are permanent and cannot be reversed, and several conditions (subscriptions, store credit, B2B links, vaulted cards, multipass, redaction) block a merge outright.
  • Klaviyo does not merge on external_id, so a clean merge in Shopify does not propagate on its own; you reconcile the profiles by email.
  • You have finished only when the merged customer's order count equals the sum of the originals and your attributed-revenue totals stop disagreeing between systems.

What you need

  • Shopify admin access with the Customers permission, plus the read_customers, read_customer_merge and write_customer_merge API scopes if you are working through the Admin GraphQL API at scale.
  • A private API key for your ESP or CRM: a Klaviyo private key with profile read and write, or a HubSpot private app with CRM contact scopes.
  • A spreadsheet or a small script (Python, a notebook, or Google Sheets) to group and diff the exported records.
  • A full export of your Shopify customers taken immediately before you start, as your only rollback path.

Steps: find the duplicates and merge them without losing history

  1. Export every customer with its identifiers and order count. From the Shopify admin, open Customers and use Export, or pull them through the Admin GraphQL API so you also get the fields the CSV omits. Request email, phone, numberOfOrders and amountSpent for each record.
    query {
      customers(first: 250, query: "") {
        edges {
          node {
            id
            email
            phone
            numberOfOrders
            amountSpent { amount currencyCode }
          }
        }
      }
    }
  2. Normalize the identifiers before you compare them. Lowercase and trim every email, and convert every phone number to E.164 (for example +15005550006), the same format the Klaviyo Profiles API stores in its phone_number field. Two records that look different in the raw export often collapse to one identity after this step.
  3. Cluster the collisions. Group the normalized rows by email, then by phone. Any group holding more than one distinct customer id is a duplicate cluster. Sort clusters by combined numberOfOrders so the records doing the most damage to your reporting surface first.
  4. Preview each pair before you merge it. Run the customerMergePreview query (it needs the read_customer_merge scope) to see which record Shopify will keep and whether anything blocks the merge. Read resultingCustomerId, blockingFields and customerMergeErrors; a non-empty blockingFields means you cannot merge this pair yet.
    query {
      customerMergePreview(
        customerOneId: "gid://shopify/Customer/111"
        customerTwoId: "gid://shopify/Customer/222"
      ) {
        resultingCustomerId
        defaultFields { ... }
        alternateFields { ... }
        blockingFields
        customerMergeErrors { errorFields message }
      }
    }
  5. Merge in Shopify. For a handful of records, use the admin merge screen and search for the partner profile by first name, last name, email, phone or credit card number. For volume, call the customerMerge mutation and set overrideFields to control which record's attributes win. The mutation returns a resultingCustomerId that the documentation says to treat as authoritative, plus a job you poll because the merge runs asynchronously.
    mutation {
      customerMerge(
        customerOneId: "gid://shopify/Customer/111"
        customerTwoId: "gid://shopify/Customer/222"
        overrideFields: {
          customerIdOfFirstNameToKeep: "gid://shopify/Customer/111"
          customerIdOfLastNameToKeep: "gid://shopify/Customer/111"
        }
      ) {
        resultingCustomerId
        job { id done }
        userErrors { field message }
      }
    }
  6. Reconcile the ESP or CRM, because the merge does not travel with the customer. Klaviyo keys identity on email and phone, not on the Shopify customer id, so merging in Shopify does not merge the matching Klaviyo profiles. Where duplicates already synced, call the Klaviyo merge endpoint to fold the source profile into the destination.
    curl -X POST https://a.klaviyo.com/api/profile-merge \
      -H "Authorization: Klaviyo-API-Key YOUR_PRIVATE_KEY" \
      -H "revision: 2024-10-15" \
      -H "content-type: application/json" \
      -d '{
        "data": {
          "type": "profile-merge",
          "id": "DESTINATION_PROFILE_ID",
          "relationships": {
            "profiles": { "data": [{ "type": "profile", "id": "SOURCE_PROFILE_ID" }] }
          }
        }
      }'

Why the two systems disagree about who a customer is

Shopify and your ESP resolve identity on different keys, and that gap is where duplicates hide. Shopify's merge tooling matches on name, email, phone and card, and treats a customer object as the unit. Klaviyo ranks identifiers as profile id, then external id, then email, then phone number, and its background identity resolution consolidates profiles when it sees an email address, phone number or device id recognized together, per Klaviyo's identity resolution documentation. The trap sits one level down: the Klaviyo Profiles API overview states plainly that external_id is not involved in profile merging, so its use can lead to duplicate profiles. If your integration writes the Shopify customer id as the external id and expects that to dedupe, it will not. Reconcile on email instead.

Klaviyo will hold up to five email addresses on a single profile through its multi-email feature, with one designated as the primary, as described in Klaviyo's multi-email profiles article. That helps once profiles are merged, but it does not find the duplicates for you.

Merge surfaceBulk?Reversible?Keys onBlocks on
Shopify admin merge screenNo, one pair at a timeNoName, email, phone, cardSubscriptions, store credit, B2B, vaulted card, multipass, redaction
Shopify customerMerge mutationYes, scriptable per pairNoCustomer ids you passSame conditions, surfaced in blockingFields
Klaviyo POST /api/profile-mergeYes, one source per callNoEmail and phoneSource profile is deleted after merge

Troubleshooting

  • The preview returns blocking fields. A Shopify merge is refused when either record has, or has ever had, a subscription contract, is a B2B customer linked to a company or past B2B orders, holds a vaulted credit card or a store credit account, uses multipass login, or is deleted or under a data-redaction request. Resolve or migrate that specific condition before retrying; the customerMergePreview reference returns the exact blocking fields per pair.
  • The wrong record was kept. When neither customer has an email, customerTwoId is the one kept. Otherwise let the preview tell you and use overrideFields to pin the first name, last name and other attributes you want to survive; the customerMerge mutation reference documents the override input.
  • Duplicates reappear after you merge. If new duplicates keep spawning, the source is upstream: guest checkout with a different email case, a POS lane that captures phone only, or an app writing profiles keyed on external_id. Fix the capture path, not just the records.
  • Klaviyo still shows two profiles. The Shopify merge does not cascade. Use Klaviyo's profile-merge endpoint, which queues an asynchronous task that folds the source profile into the destination and then deletes the source. It cannot be undone, so confirm the destination id first.

How to verify it worked

Prove the merge with counts, not with a spot check.

  • Order history is intact. On the resulting customer, numberOfOrders equals the sum of the two originals and amountSpent equals their combined total. If it does not, orders were stranded and you kept the wrong record.
  • Identity collisions are gone. Re-run the clustering from step 3 against a fresh export. The count of clusters holding more than one customer id should fall to near zero, and stay there on the next export.
  • Downstream counts converge. Your Klaviyo profile count drops by roughly the number of merges, and your repeat-purchase rate and predicted lifetime value stop reading customers as first-time buyers. Split order history is a common reason Klaviyo predicted CLV misjudges your store.
  • Revenue reconciles. Attributed revenue that was scattered across duplicate profiles now lands on one, which is the first thing to check when you reconcile Klaviyo revenue against Shopify numbers.

Sources

M
Miloš Mitrović
Email Marketing for Ecommerce

Have a question or a project?

Whether it is about this post or a system you want built, I'm happy to talk.

Get in touch

404

Post not found. It may have been moved or the link is incorrect.

← Back to the blog
Summarize with AI
ChatGPT, Perplexity, and Grok open with the prompt ready to run. Claude, Gemini, and Copilot open a chat with the prompt copied; press Ctrl+V (Cmd+V on Mac) to paste. The full text is included, so it works even without web access.