Lifecycle & marketing
Sync Shopify customers to a Meta Custom Audience
Your customer list lives in Shopify and your targeting lives in Meta, and the only thing connecting them is a CSV someone exports and re-uploads every few weeks. Here's how to wire Shopify customers to a Meta Custom Audience that stays current on its own — backfilling your existing list, adding new buyers, and (the part a CSV never does) removing people when they unsubscribe or get deleted.
- Shopify
- Meta Ads
- n8n
The manual way today
Every couple of weeks someone exports customers from Shopify, opens the CSV, maybe filters out the obvious unsubscribes, and uploads it to a Meta Custom Audience to refresh the lookalike source and the 'exclude existing customers' list. New buyers from the last two weeks aren't in Meta yet. People who unsubscribed since the last export are still sitting in the audience, because a CSV upload only ever adds — it never removes.
Your lookalike models are trained on a customer list that's perpetually a few weeks stale, so they drift from who's actually buying now. Your 'exclude customers' setup leaks, so prospecting ads keep paying to acquire people you already own. And the unsubscribes that never get pulled are a quiet compliance liability — you're targeting people who asked you to stop, which is exactly the thing a deletion request expects you to have handled.
What running looks like
Your Shopify customer list and a Meta Custom Audience stay mirrored without anyone touching a CSV. A new marketing-consented customer lands in the audience within a minute; an unsubscribe or a deletion request pulls them out. The audience is a live source for lookalikes and a clean exclusion for prospecting, and a scheduled reconcile catches any silent API drift before it matters.
01The build
How to build it
Decide what the audience is for, then create it empty
Needs a humanTwo real jobs: a lookalike seed (Meta models your best customers to find more like them) and a prospecting exclusion (stop acquiring people you already own). They can be one all-customers audience, or several — e.g. a high-value tier off total_spent, a one-time-buyers tier, and a category tier off product tags — so each seeds its own lookalike. In Meta Ads Manager create each as a Custom Audience of type Customer List, leave it empty, and copy the audience ID; that ID is what the API writes to.
Backfill your existing customers once
Runs itselfBefore the live sync, load the customers you already have. Pull them with Shopify's Admin API — a GraphQL bulkOperationRunQuery is the right tool for a large list, since it exports to a file instead of timing out on pagination. Filter to customers with marketing consent (emailMarketingConsent state SUBSCRIBED), map each to the right audience from step one, then upload in batches of up to ~10,000 hashed rows per call. This is a one-time job; run it, confirm the count, then never touch it again.
Keep it current: catch new and changed customers
Runs itselfRegister the Shopify customers/create and customers/update webhooks pointing at an n8n webhook node. create covers new signups and first-time buyers; update is what catches a consent change, a tier-defining new order total, or an email correction. Guest checkouts that don't create a marketing-consented customer simply won't qualify in the next step — which is correct, not a gap.
Normalize and SHA-256 hash the identifiers
Runs itselfMeta only accepts hashed PII for customer-list audiences, and the match rate collapses if normalization is off. Email: trim and lowercase. Phone: strip to digits and prepend country code (e.g. 13105551234). Name/city/state: lowercase, trim. Zip: first five digits. Country: two-letter lowercase. SHA-256 each value to lowercase hex in an n8n Crypto node. In the upload, the schema keys are unsuffixed — EMAIL, PHONE, FN, LN, CT, ST, ZIP, COUNTRY — it's the values you send that are the SHA-256 hashes, not the key names. Send every key you have; more keys means a higher match rate.
Add and update customers — gated on marketing consent
Runs itselfPOST to the Marketing API at /{AUDIENCE_ID}/users with a schema array naming your keys and a data array of hashed rows. Only push customers who actually consented to marketing (Shopify's emailMarketingConsent / smsMarketingConsent), so the list stays defensible. The add is idempotent — re-sending an existing customer is a no-op — so you don't need to check whether they're already in before adding.
Remove people the moment they should leave (the part CSVs skip)
Runs itselfThis is what a manual upload structurally cannot do. On customers/update where consent flipped to UNSUBSCRIBED, and on the customers/delete and GDPR customers/redact webhooks, DELETE that hashed customer from every audience via the same /{AUDIENCE_ID}/users endpoint. This keeps the lookalike source clean and, more importantly, means someone who asked you to stop is actually gone from your Meta targeting — not just from your next export.
Seed lookalikes and set the audience as a prospecting exclusion
Needs a humanThe config that turns the sync into results, and Meta won't do it for you: create a Lookalike Audience from each synced source (1% for tight, higher for reach), and use those in prospecting ad sets. Then add the all-customers audience under Excluded audiences on every acquisition campaign so you stop paying to prospect existing customers. Bake the exclusion into your ad-set checklist so a duplicated campaign inherits it.
Verify match rate and reconcile counts on a schedule
Runs itselfMeta takes a few hours to match and populate a customer-list audience, so don't judge it on day one. After a day, confirm the size is climbing and the match rate is reasonable — a low rate almost always means a hashing/normalization bug, not bad data. Then run a weekly n8n job that compares your Shopify consented-customer count against the audience size; a widening gap is how you catch a silently failing webhook or a rejected API batch before it rots the whole list.
02The stack
What it’s built on
- Shopify
- Source of truth for who your customers are and whether they consented. The bulkOperationRunQuery export drives the backfill; customers/create, customers/update, customers/delete, and the GDPR redact webhook drive the live add/remove.
- Meta Ads
- Holds the Custom Audience, builds lookalikes from it, and applies it as a prospecting exclusion. The Marketing API's /{audience_id}/users endpoint is what you add and remove hashed customers through.No special tier needed — customer-list Custom Audiences, lookalikes, and the exclusion field are standard in any ad account. You just need a system-user token with ads_management access to call the API; nothing to buy.
- n8n
- The glue: runs the one-time backfill, catches the webhooks, normalizes and hashes the PII, calls the Marketing API to add or remove, and runs the weekly reconcile.n8n is open-source and self-hostable for free; their cloud tier just saves you running it yourself. Hashing customer PII on infrastructure you control (self-hosted) is the cleaner privacy story.
03Questions
Before you build
How is this different from just uploading a CSV to Meta?
Two ways that matter. First, it's continuous — new customers land in the audience within a minute instead of waiting for the next export, so your lookalike source reflects who's buying now. Second, and the bigger one, it removes people. A CSV upload only ever adds, so unsubscribes and deletions pile up in the audience forever. This pulls them out the moment Shopify says they left.
Isn't this the same as excluding recent purchasers from retargeting?
Related but different, and they pair well. Excluding recent purchasers is a short, time-boxed suppression so you don't retarget someone who just bought yesterday. This is the durable mirror of your whole customer list — used to seed lookalikes and to exclude all existing customers from prospecting. One stops wasteful retargeting; this one feeds acquisition and keeps it from re-buying customers you already have.
Do I need consent to upload my customer list to Meta?
Treat it like any PII you share with Meta. The clean approach: only push customers who accepted marketing (Shopify tracks this per channel), honor opt-outs and deletion requests by removing them — which step six does automatically — and check your regional rules, since GDPR and CCPA expectations are stricter. 'Only consented customers, removed when they leave' is both the compliant posture and the one that keeps your match data clean.
Can I sync segments separately, like VIPs vs one-time buyers?
Yes, and it's worth doing. Create a Custom Audience per tier in step one and route each customer to the right one by Shopify total_spent, order count, or product tags. A lookalike built from your high-value customers finds better prospects than one built from everyone, and tiered exclusions let you keep prospecting customers you'd actually want to win back while excluding the rest.
Will this cover my existing customers or only new ones going forward?
Both, in two parts. Step two is a one-time backfill that loads your current list via a Shopify bulk export, so day one starts with everyone who already consented. After that the webhooks keep it live. Skipping the backfill is the common mistake — you'd start with an empty audience and a lookalike that has nothing to learn from for weeks.
Can I build this myself, or should you build it?
Every webhook, the bulk-export approach, the exact hashing normalization, and the add/remove endpoints are spelled out above — so if you're comfortable with APIs and have an afternoon, build it. If you'd rather not own a customer-PII sync with a backfill and a removal path, we'll build it on the Shopify, Meta, and n8n accounts you already use, document it, and hand you the keys: you own the workflow outright, no monthly retainer. It starts with a free 20-minute teardown, not a contract.
Related recipes
Lifecycle & marketing
Exclude recent purchasers from Meta retargeting
Your retargeting ad sets keep serving 'come back and buy' creative to people who already bought yesterday. Here's how to wire Shopify's paid-order event to a Meta Custom Audience, set it as an exclusion on every retargeting ad set, and time-box it so buyers flow back in when it's actually worth re-selling to them.
- Meta Ads
- Shopify
- n8n
Lifecycle & marketing
Build a win-back flow for customers lapsed 90 days
A customer who used to order every month quietly stops, and nothing in your store flags it — you only chase brand-new strangers while people who already loved the product drift to a competitor. Here's how to build a Klaviyo win-back flow that catches them on real purchase cadence (not a blanket 90 days), reconnects before it discounts, and self-exits the moment someone buys. It's a native Klaviyo build — no n8n or glue layer required.
- Klaviyo
- Shopify
Lifecycle & marketing
Build an abandoned cart flow with Klaviyo and n8n
Klaviyo's built-in abandoned cart flow is good — when its onsite tracking actually sees your checkout. When it doesn't (a headless storefront, a custom cart, a checkout the snippet never loads on), the recovery emails just don't fire. Here's how to read the abandonment straight from Shopify with n8n, guard it so you never email someone who already bought, and hand Klaviyo a reliable event to send on.
- Klaviyo
- n8n
- Shopify
Reporting & dashboards
Automate a daily ad spend vs revenue report
To know if yesterday's ad spend paid off, someone opens Ads Manager for the spend, opens Shopify for the revenue, and does the division in their head — across two timezones and two definitions of a 'sale.' This does it for you. Once a day it pulls yesterday's spend from Meta, yesterday's revenue from Shopify, lines the two day-windows up so they're actually comparable, computes blended ROAS (and shows Meta's own attributed ROAS next to it so the gap is visible), and posts one clean line to Slack. Build it yourself from the steps below, or we build it on your own accounts, reconcile the windows and the currency, and hand you the keys so you own it with no monthly fee. Free teardown of how you check whether ads are working first.
- Meta Ads
- Shopify
- Slack