Setting up CRM Sync
Everything a new user does, in order. Eight steps, about forty minutes. Seven are yours; the last is a release step, run by whoever holds that role on your team.
The steps
Step 1 You
Install the app
Open Get the App and install CRM Sync. It runs as its own window on desktop, or from the Home Screen on iPhone and iPad. If your browser doesn't offer to install it, reload the page once — the prompt only activates after the service worker finishes registering.
Step 2 You
Sign in with the account you purchased on
Your entitlement is attached to the account that bought the app. If Keys or Teams tell you there's no entitlement and you have just purchased, give it a minute and reload before contacting support.
Step 3 You
Generate your admin key
Open Your Keys and generate it. This key is minted for you — it isn't fetched from Google or Shopify. It is shown once, so put it in a password manager the moment you see it.
Rotate or revoke it whenever you like; every change is recorded in the fingerprint audit ledger.
Rotate your key
Open Your Keys and press Rotate key. That is the whole operation — no terminal, no developer, no ticket.
Same call from a terminal — for a developer or security reviewer
curl -s -X POST "https://crm-sync.dev/tenant/keys/rotate" \
-H "Authorization: Bearer <your login token>" \
-H "Content-Type: application/json" \
-d '{"shop":"your-store.myshopify.com"}'
First-time setup and reset are the same operation: the response carries your new crm_t_ key exactly once, and every previous key for the shop is revoked in the same call — the new key is written before the old ones die, so there is never a keyless gap.
How is this secure — and why only you ever see it
Your key is generated inside the Cloudflare Worker with hardware randomness (crypto.getRandomValues) and returned once, over TLS, in that response only. We keep no readable copy: what lands in storage is the key's SHA-256 fingerprint, which is what the audit ledger and every on-screen read-back show — enough to prove which key acted, never enough to reconstruct it. Platform secrets live as encrypted Cloudflare Worker secrets that never leave the runtime, and no dashboard, log, or support channel can display your key back to you. That's the deal: if you lose it, nobody — including us — can recover it. You rotate it, the old key dies instantly, and the ledger records the change.
Step 4 You
Bring your own Xano instance
CRM Sync stores your product, identity, and consent records in Xano — and the instance is yours, not ours. This is a requirement, not an upgrade: you create the workspace on your own Xano account, and you hand CRM Sync a key you can revoke. It is the one setup step that decides where your data actually lives.
Why we ask you to own it. If your records sat in our workspace, then "we never see your data" would be a promise. With the instance in your account it is a fact about the wiring: we hold a scoped API key against a database you control, you can read every row without asking us, and revoking the key ends our access the same minute — no ticket, no deletion request, no trust required. It is also the honest answer when your security reviewer asks who can read the data at rest.
Three values go into Config → Xano:
https://<instance>.xano.io — shown on the instance in your Xano dashboardBoth credentials are encrypted before they are written — AES-256-GCM under a key that never leaves the runtime — so what sits in storage is ciphertext, not your token. Rotating the key on your side and pasting the new one is a normal, expected operation; nothing else has to change.
Where Xano ends and Cloudflare begins
Two different signing systems are in play, and they are not redundant — they answer different questions.
/.well-known/jwks.json, so an agent, a storefront, or your own service verifies it without a shared secret and without calling us.The short version: Xano keeps what is private unreadable; Cloudflare makes what is public verifiable. Encryption hides a payload from whoever carries it; a signature proves origin to whoever receives it — which is why a third party can check an EdDSA token against a published key, and why nobody can check a JWE without the key that opens it. Your Xano credential never leaves the worker, and no signing key of ours is ever needed to read your own data.
Step 5 You
Connect your store
Install the CRM Sync app on your Shopify store. The wizard doesn't take your word for this: it checks that your store has a live app session and then makes a real Admin API call to read your shop name back. The row turns solid only when that call actually succeeds.
Step 6 You
Connect Google Analytics
Open Connect Google and fill the cards in order. Each value and where it lives is in the table below. The Measurement Protocol card is verified live against Google when you save — if it goes green, the credentials genuinely work.
The BigQuery card additionally needs a one-time grant on your side: give crm-sync-runtime@crm-sync-prod.iam.gserviceaccount.com the BigQuery Data Viewer role on your export dataset. That single read-only grant on one dataset is the maximum access that ever leaves your side, and you can revoke it at any time.
Optional You
Not on Webflow? Connect Astro, Next.js — any framework
The site never holds your data, so there is no framework-specific setup. In Config, leave the Webflow CMS Sync section empty — it's only the publishing mirror for Webflow sites. Then add the helmet as the first script in your layout's <head>:
<script src="https://crm-sync.dev/embed/stack-loader.js"></script>
Next.js: app/layout.tsx, a plain tag or next/script with strategy="beforeInteractive". Astro: your base Layout.astro. The helmet carries analytics, Consent Mode v2, and the consent bridge — don't add a separate cookie banner or GA snippet.
Google and Shopify login ride along with nothing to build: your components listen for the crm-auth-change event and read crm_auth_token. Optional chrome: /embed/crm-elements.js (nav & login elements) and /embed/footer-loader.js. Server-side, call the worker's REST API from your API routes with Authorization: Bearer <your key> from an environment variable — the key never ships in a client bundle and never appears in a URL.
Optional You
On Adobe Experience Manager?
Same contract, AEM placements. Edge Delivery Services (aem.live): add the helmet as the first script in your project's head.html — it ships with your next code sync. AEM Sites (Cloud Service): put it first in the page template's customheaderlibs.html (or a site-wide client library that loads in <head>) and publish through Cloud Manager.
<script src="https://crm-sync.dev/embed/stack-loader.js"></script>
The helmet owns consent (Consent Mode v2 + the Shopify consent bridge). If you run Adobe Launch / Tags, load it behind the helmet's consent gate the same way the Tag Manager guidance below describes — two ungated tag systems double-fire and double-count.
Login and components are identical to any other framework: crm-auth-change + crm_auth_token, and Shopify's cart web components work on AEM pages. Server-side, an OSGi service or servlet calls the worker's REST API with the bearer key from OSGi configuration — never a client library. Adobe organizations can also switch on the Adobe AEP connector in Config: the worker then POSTs versioned entitlement + consent records into your AEP dataset directly, which replaces CMS mirroring the way it would work on Webflow.
Optional You
On WordPress?
Same contract, WordPress placement. The helmet must be the first script in <head>, so don't bury it in a page builder's footer slot — add it from a tiny must-use plugin, or in your theme's functions.php:
add_action('wp_head', function () {
echo '<script src="https://crm-sync.dev/embed/stack-loader.js"></script>';
}, 1);
The helmet owns analytics and consent — skip the separate GA plugin (Site Kit, MonsterInsights) and the cookie-banner plugin for this property, or they'll double-fire what the helmet already gates.
Login and components are the same everywhere: crm-auth-change + crm_auth_token, in any theme or builder. Server-side, call the worker's REST API with wp_remote_get/wp_remote_post and the bearer key from a wp-config.php constant — never a plugin settings field, never client-side. Running WooCommerce? Deep commerce sync is Shopify-first, but WooCommerce rides the same tenant model — your key and identity plane work unchanged.
Optional You
Publishing on EmDash or Cloudflare?
Static publishes carry the helmet in the published HTML — the tag travels with every page. EmDash: add it first in the site's head include (the custom-code / head slot), and every export ships it. Cloudflare Pages / Workers: put it first in the base template's <head> before you deploy — no build plugin required.
<script src="https://crm-sync.dev/embed/stack-loader.js"></script>
A static page still gets the full plane at runtime: analytics, Consent Mode v2, and Google + Shopify login (crm-auth-change + crm_auth_token) all resolve against the worker, not the build. On Cloudflare specifically, server-side calls are first-class: a Pages Function or your own Worker calls the REST API with the bearer key stored as an encrypted environment variable on your project — Cloudflare's own secret store, in your account, never in the static bundle or a URL.
Optional You
On GitHub Pages?
The purest case — a repo of HTML, no server at all. Put the helmet first in the shared head: Jekyll's _includes/head.html (or _layouts/default.html), or each page's <head> for a plain-HTML repo. Login, consent, and analytics all run browser-side against the worker, so no key exists anywhere in the repo — a public repo stays publishable.
<script src="https://crm-sync.dev/embed/stack-loader.js"></script>
Because Pages has no server runtime, anything that needs the bearer key runs in GitHub Actions instead — the key lives as an Actions secret and your workflow calls the worker's REST API at build or release time (the same pattern our SBOM registry CI push uses). Our own public docs site runs on GitHub Pages with this exact helmet, so the pattern ships proven.
Reference The platform underneath
Why Cloudflare Workers
The helmet, the API, and the whole identity/consent/commerce plane run on Cloudflare Workers — the same platform Shopify and Webflow independently built their own commerce runtimes on. That isn't a partnership claim; it's three companies choosing the same edge platform, each documenting it themselves:
- Shopify Engineering — How We Built Oxygen — Shopify's own account of building Oxygen (their Hydrogen storefront hosting) on Cloudflare's
workerd. - Shopify Oxygen architecture — the same, in Shopify's official developer docs: a worker-based edge runtime.
- Webflow Cloud edge environment — Webflow's docs: Webflow Cloud runs your app on Cloudflare Workers.
- Cloudflare — powering platforms on Workers — Cloudflare's own writeup of how platforms build on the Workers runtime.
All four are first-party — Shopify, Webflow, and Cloudflare stating it about themselves. Your worker is your own deployment on that platform, isolated from theirs; sharing a platform is not sharing infrastructure.
Reference The identity layer
One login, either provider — the identity that feeds UCP & Google Merchant
Sign in with Google or Shopify — whichever the customer already has — and both resolve to one identity. The match is the email address: a Google sign-in and a Shopify sign-in for the same person land on the same record, so consent, entitlements, purchases, and agent mandates follow the person, not the login button. The two provider identities (Google and Shopify) sit side by side on that one record; there is never a duplicate account per method.
Identity resolves in the service, not the storefront. The login authenticates against Xano (the identity store) and the worker issues a signed session token; the storefront is downstream of that, not the source of truth.
The Shopify bridge, from either door. When someone signs in with Google and is new, the worker mints a Shopify customer for them automatically — no Shopify login required. That is the “push to Shopify via Xano” step: a real Shopify customer identity created from any login, which is exactly what the Google Merchant strategy consumes downstream (customer, orders, and the agent-catalog feed) and what a UCP agent authenticates as when it acts under a mandate.
storefront_users record; provider ids on user_claimsNo fee-based login service to buy: Google and Shopify are the login, Xano is the record. One credential, every surface — the same session rides into your Webflow site, Shopify storefront, and embeds.
Step 7 · Optional You
Connect the agent plane (UCP · Google Merchant)
If AI agents will find, compare, or buy from your store, wire the agent-catalog plane. Install the UCP CLI and create your local profile — it's presented on every merchant-scoped request:
npm install -g @shopify/ucp-cli # Node 18+; installs the ucp command
ucp profile init --name agent # one-time local profile (~/.ucp/profiles/agent)
ucp doctor # health check — should report ok: true
Then confirm your store speaks UCP: ucp discover --business https://your-store-domain should list dev.ucp.shopping services. If the CLI ever reports Missing profile uri, the sequence to remember is ucp doctor → ucp profile init --name agent → ucp doctor.
Finally, open the Configurator's Google Merchant · UCP section (under ERP & WMS). The Shopify Global Catalog MCP endpoint and the Google Merchant MCP endpoint are pre-filled; set the Agent / Dark Warehouse feed to Automated only when you want the products feed to run with no human in the loop — every feed event is logged to your ERP, WMS, Remittance, and Consent Management systems. What ships in this plane is public in the app's SBOM.
Search ranking note: broad catalog queries rank on Shopify's Inferred fields (description, attributes, tech specs), which build from your published product data — richer machine-readable copy on your product pages is the lever.
Step 8 Release Engineer
Going live
The final button is deliberately not the same person's as the setup. Going live is restricted to the Release Engineer role, because whoever reviews a build should not be the one who ships it. Once your connections are verified, the go-live wizard reads "Ready for release" — and that is the finished state for the setup work.
Release Engineer is a role, not a vendor. A colleague on your side can hold it, and on most teams that is the right answer — nothing about this step requires an outside company. If you would rather we ran the cutover with you, that is support on an application you bought, included with your purchase: no supplier record, no statement of work, no vendor onboarding. Reach us at support@crm-sync.dev.
Where to find every key
Nine values to copy — two of them optional — plus one the app mints for you. Anything not in this table — your Shopify access token, your OAuth credentials — is handled by the connect flows and never pasted by hand.
| Key | Looks like | Where to get it | Where it goes | Needed? |
|---|---|---|---|---|
| GA4 Measurement ID | G-XXXXXXXXXX |
analytics.google.com → Admin (gear, bottom-left) → Data Streams → your web stream. The Measurement ID is top-right on the stream panel. | Connect Google → GA4 Measurement Protocol | Required |
| GA4 Measurement Protocol API secret | a ~22-char string |
Same Data Stream panel → Measurement Protocol API secrets → Create. Copy the secret value — Google shows it once. | Connect Google → GA4 Measurement Protocol | Required |
| GA4 Property ID | a number, e.g. 545206186 |
analytics.google.com → Admin → Property Settings. It is the number under the property name, not the G- ID. Keep it — your export dataset is named after it. | Connect Google → GA4 Property | Required |
| GA4 → BigQuery export dataset | analytics_ |
analytics.google.com → Admin → BigQuery Links → Link → pick the project → enable Daily. Google then creates the dataset; its name is analytics_ plus your Property ID. | Connect Google → GA4 → BigQuery export | Required for reports |
| Merchant Center ID | a number, e.g. 1234567 |
merchants.google.com → the account selector, top-right. Only needed if you run a product feed. | Connect Google → Merchant Center | Optional |
| Tag Manager container ID | GTM-XXXXXXX |
tagmanager.google.com → your container. The GTM- ID is at the top of the workspace. You do not need this to measure — CRM Sync loads GA4 itself. Add it only if you already keep your own marketing tags in a container, and we load that container behind the same consent gate. | Connect Google → Tag Manager container | Optional |
| Xano instance base URL | https:// |
Your own Xano account — the instance address shown on the instance in your Xano dashboard. You create the instance; we never provision one for you. | Config → Xano | Required |
| Xano API key | a long opaque token |
Xano → generate a Metadata API token scoped to the workspace CRM Sync should use. This is not your Xano login, and it is revocable from your side at any time. | Config → Xano | Required |
| Xano workspace ID | a number, e.g. 4 |
The workspace number in the Xano URL while that workspace is open. | Config → Xano | Required |
| Your admin key | crm_t_… |
You do not fetch this one from anywhere — CRM Sync mints it for you. Open Your Keys and generate it. It is shown ONCE; put it straight in a password manager. Rotate or revoke it any time; every change lands in the audit ledger. | Your Keys (/keys) | Included with purchase |
Questions people actually ask
Can you send me my admin key? Do I create one in Cloudflare?
Neither — and that's deliberate. Nobody can send you your key, including us: it's minted by you in Your Keys after signing in with the account you purchased on, shown exactly once, only to you. Your purchase grants the entitlement that unlocks the Generate button; the key itself never travels through email, chat, or support. And no, you never touch wrangler or Cloudflare — that layer belongs to the platform's root credential, which your shop-scoped key is deliberately not. If you lose yours, rotate it: the new one appears once, and every previous key for your shop dies in the same call.
Do I have to bring my own Xano instance? Can't you just host it?
You bring your own, and it is the one thing we won't do for you. Your records live in a Xano workspace on your account, so "we never see your data" is a fact about the wiring rather than a promise we're asking you to believe: you can read every row without asking us, and revoking the API token ends our access that minute — no deletion request, no ticket. Hosting it ourselves would make your data our liability and your exit our decision. Setting it up is one step and a few minutes, and we'll walk you through it if you want.
Do I need Google Tag Manager?
No — not to measure. CRM Sync delivers GA4 through gtag in its own stack loader, so a container is never required. Tag Manager is optional: if you already keep your own marketing tags in a container, paste its GTM- ID into Connect Google and we load that container behind the same consent gate as everything else. Point it at your own tags, not at the GA4 property you gave us above — configuring the same property twice double-counts every hit.
The Connect Google page says my store is something.myshopify.com — is that wrong?
That's your store's internal Shopify identifier, which stays the same even after you connect a custom domain. Once your store connection is live, the page shows your real address instead. If it's showing a store you don't recognise, you likely opened the page with the wrong ?shop= in the URL.
Why is a card still "Pending" when I filled it in?
Pending means the value is saved but not yet verified, or not yet set. "Action needed" means we tried and it genuinely failed — the card tells you which grant or value to fix. Only the Merchant Center card is optional; it never blocks your overall status.