Reference

CRM Sync — Security Audit & Paired Data Requirements

Date: 2026-05-18 Version: 1.1 Worker Version: dac8f178-f6ed-4a11-96be-f640a67c64ae


1. Security Audit Summary

1.1 Route Auth Coverage (Post-Hardening)

All 67 routes verified. Every write endpoint and admin endpoint now requires authentication. Per-tenant admin keys provide granular access control with platform key fallback.

Auth Layers

LayerMechanismProtects
Bearer TokenAuthorization: Bearer <ADMIN_KEY> or per-tenant admin_keyAll /admin/*, /sync/*, /config POST, /tags/create, /webhooks/upsell, /platform/config
Admin Key (query)?key=<ADMIN_KEY> in URL/setup, /onboarding, /onboarding/setup, /settings
JWT (user session)httpOnly cookie, HS256 signed/auth/me, /auth/profile, /auth/delete-account, /ucp/*, /tags/*, /segment/*
HMAC-SHA256X-Shopify-Hmac-SHA256 header/webhooks/customer-update, /webhooks/customer-create, /gdpr/*, /api/webhooks
OAuth StateUUID-keyed KV nonce (oauth_state:{uuid}), single-use, includes shop/auth/callback, /auth/webflow/callback, /auth/google/callback, /auth/shopify/callback
Cloudflare AccessOTP email verificationWorker admin URLs (browser access)

Complete Route Matrix

#MethodPathAuthType
1GET/healthNone (public)Health check
2POST/configBearer tokenAdmin
3GET/configNone (secrets masked)Read-only
4GET/admin/tenantsBearer tokenAdmin
5GET/auth/installNone (OAuth initiation)OAuth
6GET/auth/callbackOAuth state verificationOAuth
7GET/embed/footerNone (public embed)Embed
8GET/embed/complianceNone (public embed)Embed
9GET/embed/accountNone (public embed)Embed
10GET/embed/dashboardNone (public embed)Embed
11GET/setupAdmin key (query)Admin UI
12GET/settingsAdmin key (inside handler)Admin UI
13GET/onboardingAdmin key (query)Admin UI
14GET/onboarding/setupAdmin key (query)Admin UI
15POST/onboarding/auto-setupBearer tokenAdmin
16GET/auth/webflow/connectNone (OAuth initiation)OAuth
17GET/auth/webflow/callbackOAuth state verificationOAuth
18GET/api/xano/actionsNone (public manifest)Read-only
19POST/auth/signupNone (registration)Auth
20POST/auth/loginNone (login)Auth
21GET/auth/meJWT (inside handler)User
22POST/auth/logoutNone (clears cookie)Auth
23GET/auth/google/loginNone (OAuth initiation)OAuth
24GET/auth/google/callbackOAuth state verificationOAuth
25GET/auth/shopify/loginNone (OAuth initiation)OAuth
26GET/auth/shopify/callbackPKCE verificationOAuth
27POST/auth/profileJWT (inside handler)User
28POST/auth/delete-accountJWT (inside handler)User
29POST/auth/forgot-passwordNone (email-based)Auth
30GET/auth/reset-passwordToken in URLAuth
31POST/auth/reset-passwordToken in bodyAuth
32POST/auth/consent-syncNone (see note)Consent
33GET/ucp/consent-historyJWT (inside handler)User
34POST/ucp/tagsJWT (inside handler)User
35POST/ucp/translateJWT (inside handler)User
36POST/segment/searchJWT (inside handler)User
37GET/segment/statsJWT (inside handler)User
38POST/segment/countJWT (inside handler)User
39POST/webhooks/customer-updateHMAC-SHA256Webhook
40POST/webhooks/customer-createHMAC-SHA256Webhook
41POST/webhooks/webflow-item-changedWebflow webhookWebhook
42POST/api/webhooksHMAC-SHA256Webhook
43POST/gdpr/customer-redactHMAC-SHA256GDPR
44POST/gdpr/data-requestHMAC-SHA256GDPR
45POST/gdpr/shop-redactHMAC-SHA256GDPR
46GET/tagsJWT (inside handler)User
47GET/tags/userJWT (inside handler)User
48POST/tags/userJWT (inside handler)User
49POST/tags/createBearer tokenAdmin
50POST/admin/init-tag-systemBearer tokenAdmin
51POST/admin/xano-schemaBearer tokenAdmin
52POST/admin/xano-reseedBearer tokenAdmin
53POST/admin/adobe-schemaBearer tokenAdmin
54POST/webhooks/upsellBearer tokenAdmin
55POST/admin/register-webhooksBearer tokenAdmin
56POST/admin/webflow-ensure-fieldsBearer tokenAdmin
57POST/admin/webflow-sync-testBearer tokenAdmin
58GET/admin/webflow-testBearer tokenAdmin
59GET/admin/shopify-customersBearer tokenAdmin
60GET/admin/shopify-testBearer tokenAdmin
61POST/sync/customersBearer tokenAdmin
62POST/sync/webflowBearer tokenAdmin
63GET/admin/tenantsBearer tokenAdmin
64GET/platform/configBearer tokenAdmin
65POST/platform/configBearer tokenAdmin
66POST/admin/provision-regionBearer tokenAdmin
67POST/admin/adobe-schemaBearer tokenAdmin

Notes

1.2 Fixes Applied (2026-05-17)

RouteBeforeAfter
POST /admin/init-tag-systemOpenBearer token
POST /admin/xano-schemaOpenBearer token
POST /admin/xano-reseedOpenBearer token
POST /admin/register-webhooksOpenBearer token
POST /admin/webflow-ensure-fieldsOpenBearer token
POST /admin/webflow-sync-testOpenBearer token
GET /admin/webflow-testOpenBearer token
GET /admin/shopify-customersOpenBearer token
GET /admin/shopify-testOpenBearer token
POST /sync/customersOpenBearer token
POST /sync/webflowOpenBearer token
POST /tags/createOpenBearer token

2. Paired Data Requirements

2.1 System Pairs — CRM Sync (Webflow-Primary Gateway)

CRM Sync uses Webflow as its primary headless gateway and Shopify as the commerce data source. Data flows bidirectionally between 7 systems.

Webflow (Gateway) ←→ Worker ←→ Shopify (Commerce)
                       ↕
              Xano (Source of Truth)
                       ↕
              GA4 / Adobe AEP / Resend

2.2 Data Pair Matrix

Each pair defines: what data crosses the boundary, which direction, what trigger, and what security contract.

Thesis — keep the castle and the moat; distribute the gold. The incumbent's mistake is not having walls — it is hoarding all the gold in one vault behind them. That vault is the blast radius: one breach takes everything, and the hoarding also locks the value away from the people who need it. This architecture keeps the castle and the moat — Cloudflare's WAF, DDoS, and edge governance still stand — but spreads the gold across many pockets: PCI in Shopify, PII/consent in Xano, a draft mirror in Webflow, grants that verify with a public key. The result is **security and accessibility in the same move** — nothing concentrated to steal, everything reachable to those authorized:

"Zero Trust" is simply the admission that a hoarded, trusted vault is how you get breached.

Substrate ≠ Security. Webflow and Xano are where this runs — not what makes it safe. The controls live in the architecture below: HMAC-SHA256 mandates, PKCE + OIDC auth, JWT sessions, per-action authorization, offline-verifiable Ed25519 grants, real-time consent. These are the same cryptographic primitives enterprise systems use — and the agentic model (A2A read vs AP2 spend, per-action gating, revocable consent) is one most enterprise CRMs do not have at all. No-code substrate; enterprise-grade boundary logic. Judge the boundary logic, not the vendor logos.

Closed-boundary software and AI governance. A closed, monolithic, single-vendor boundary governs by containment — a fixed perimeter, a fixed release cadence, a policy engine you cannot reach into. For human-speed CRM that is tolerable. For governing an autonomous, real-time AI actor it is a liability, because AI incidents move faster than a closed system can respond. A closed boundary denies the three capabilities AI governance requires:

Implication: you cannot govern an autonomous, real-time actor with a system that changes only on the vendor's schedule, recovers only on the vendor's timeline, works only when every component is up, and charges you more the more the AI works. AI governance requires a layer you can heal, fall back, forward-deploy, and scale — without a toll booth — at the speed the AI operates, which a closed boundary, by definition, forbids. This is the "Risk Management for AI Incident Remediation" framework the architecture names.

Layer model (canonical: the published architecture at <https://crm-sync.webflow.io>). Five layers, each with a distinct job; no single one is a hard dependency — the three-leg design degrades gracefully if any is unavailable:

  1. Cloudflare — edge governance (the "God layer"). Edge OTP + admin-only gating, WAF, DDoS protection, bot mitigation, secrets, rate limits. Governs security for every request — but is not a single point of failure: entitlement grants are offline-verifiable (Ed25519 public key), writes buffer and replay, and the PWA serves cached reads.
  2. Authentication Layer. Google (OAuth + PKCE), Shopify (OIDC + PKCE), Email/Password (Xano direct).
  3. Verification Layer — Xano. Find-or-create users; the system of record for identity, PII, and consent state.
  4. Token. Worker-issued JWT (7-day TTL) — the scoped session / mandate credential.
  5. Data Layer. Webflow (Collection Sync + Entitlement — the draft-only CRM mirror, Pair 2), Shopify (metaobjects, tags, A2A/AP2 JSON — the PCI plane: cardholder data stays in Shopify's PCI scope), GA4 + Adobe/BAU (user props, UCP conversions).

Plane separation: payment/cardholder data stays in Shopify's PCI scope; identity / PII / consent are the Xano system of record; Webflow holds a draft-only CRM read-model mirror (no PCI, no Webflow commerce); Cloudflare governs but is not depended upon.

Pair 1: Shopify ↔ Xano (Customer Identity)

FieldShopify SourceXano TableDirectionTrigger
Emailcustomer.emailstorefront_users.emailShopify → XanoCron / Webhook
First Namecustomer.firstNamestorefront_users.first_nameShopify → XanoCron / Webhook
Last Namecustomer.lastNamestorefront_users.last_nameShopify → XanoCron / Webhook
Shopify GIDcustomer.idstorefront_users.shopify_gidShopify → XanoCron / Webhook
Orders Countcustomer.numberOfOrdersstorefront_users.number_of_ordersShopify → XanoCron / Webhook
Total Spentcustomer.totalSpentV2.amountstorefront_users.amount_spentShopify → XanoCron / Webhook
Countrycustomer.defaultAddress.countryCodeV2storefront_users.countryShopify → XanoCron / Webhook
Tagscustomer.tagsuser_tag_map (join table)Bi-directionalCron / Webhook / UCP
Metafieldscustomer.metafields (crm_*)Derived from tagsXano → ShopifySync

Security: HMAC-SHA256 on webhooks. Admin token (shpua_) for GraphQL. Token auto-refreshed before 60-min expiry.

Pair 2: Xano → Webflow (Customers CRM mirror — resilience leg)

The Webflow "Customers" collection is a read-model CRM mirror / backup of Shopify customer profiles — the same role Salesforce or HubSpot play (contact PII, consent state, and commerce aggregates for CRM use). It is one leg of the three-leg resilience design (Shopify ↔ Xano ↔ Webflow): if one leg is unavailable, the customer read-model still exists in the others. Mirror items are written as drafts — a back-office copy, not published to the live public site.

Plane separation (why this is a CRM mirror, not a PCI / commerce exposure):

FieldXano SourceWebflow CMS FieldDirectionTrigger
Namestorefront_users.full_namename (required)Xano → WebflowSync
Emailstorefront_users.emailemailXano → WebflowSync
First/Last Namestorefront_users.first_name/last_namefirst-name, last-nameXano → WebflowSync
Providerstorefront_users.providerproviderXano → WebflowSync
StatusDerived from tagsstatusXano → WebflowSync
Tagsstorefront_users.tags / user_tag_maptags, tag-refsXano → WebflowSync
Consent stateuser_claims.*consent-tos/privacy/cookie/marketingXano → WebflowSync
Commerce aggregatesstorefront_users.*number-of-orders, amount-spent, countryXano → WebflowSync
Shopify IDstorefront_users.shopify_gidshopify-customer-idXano → WebflowSync
Adobe fieldsuser_extras.*adobe-ecid/email-hash/sync-status/last-synced/identity-graph-idXano → WebflowSync

Security: Webflow CMS token (write-scoped, worker-held). Mirror items are drafts (off the live site). No PCI / cardholder data is ever mirrored — payment data stays in Shopify's PCI plane. The mirror performs no transactional or commerce function; it is a CRM read-model backup, comparable to a Salesforce/HubSpot contact sync.

Pair 3: Xano ↔ GA4 (Analytics Events)

EventData SentDirectionTrigger
crm_tags_updatedtags_added, tags_removed, crm_status, crm_tier, crm_segmentXano → GA4Tag mutation
crm_syncsource, synced countXano → GA4Cron sync
crm_form_submitform_type, email (DataLayer only)Client → GA4Form bridge
crm_upsellupsell_source, product_count, total_valueXano → GA4Upsell event
User Propertiescrm_status, crm_tier, crm_segment, crm_tags, crm_campaign, consent_marketing, consent_tosXano → GA4Any mutation

Security: GA4 API Secret stored in KV config. No PII sent — only tag categories and consent state. Client ID format: crm-sync.{userId}.

Pair 4: Xano ↔ Adobe AEP (CDP Streaming)

FieldXano SourceXDM PathDirectionTrigger
Email (hashed)SHA-256(email)identityMap.Email[0].idXano → AEPCustomer update
Phone (hashed)SHA-256(phone)identityMap.Phone[0].idXano → AEPCustomer update
Name (hashed)SHA-256(name)_shopifyCrmSync.hashedNameXano → AEPCustomer update
ConsentDerived from tagsconsents.marketing.email/push, consents.adID, consents.personalizeXano → AEPTag mutation
SubscriptionsDerived from tags_shopifyCrmSync.subscriptions.{type}Xano → AEPForm bridge
CommerceProduct listcommerce.productListAdds, productListItems[]Xano → AEPUpsell event
ECIDAEP responseuser_extras.adobe_ecidAEP → XanoSync result
Sync statusSync resultuser_extras.adobe_sync_statusWorker → XanoAfter push

Security: Adobe IMS OAuth (client_credentials). PII hashed with SHA-256 via Web Crypto API before transmission. Raw PII never leaves the worker. IMS tokens cached in KV with TTL.

Pair 5: Xano ↔ Resend (Transactional Email)

Email TypeData SentDirectionTrigger
Welcomeemail, name, set-password link (24h token)Xano → ResendNew Shopify-origin user
Password Resetemail, reset link (1h token)Xano → ResendForgot password

Security: Resend API key stored as wrangler secret. Reset tokens are KV-stored with TTL, single-use.

Pair 6: Worker ↔ Cloudflare KV (Config & State)

Key PatternDataSensitivityTTL
tenant:{shop}:configFull CrmSiteConfig with all credentials + admin_keyHIGHPersistent
tenant:{shop}:tag_table_idsXano table IDs (crm_tags + user_tag_map)LOWPersistent
tenant:{shop}:webflow_tags_collection_idWebflow CRM Tags collection IDLOWPersistent
tenant:{shop}:sync:customers:last_runISO timestamp of last cron syncLOWPersistent
tenants:indexTenantEntry[] (shop, region, registered_at)LOWPersistent
platform:configShared platform credentialsHIGHPersistent
adobe_token:{shop}Adobe IMS access tokenHIGH~24h
pkce:{state}PKCE code_verifierMEDIUM5 min
oauth_state:{uuid}OAuth state + shop (UUID-keyed, no global collisions)MEDIUM10 min
reset:{token}Password reset metadataMEDIUM1h / 24h

Security: KV encrypted at rest (Cloudflare managed). Secrets masked in GET /config. Short TTLs on auth state.

2.3 Baseline Data Contract

Every data pair has the following contract:

  1. Identity resolution — Email is the primary key across all systems (Shopify GID for Shopify-specific operations)
  2. Source of truth — Xano is canonical. Conflicts resolved by most-recent-write-wins.
  3. PII boundary — Raw PII stays within Worker + Xano + Shopify + Webflow CMS + Resend. GA4 and Adobe AEP receive hashed or categorized data only.
  4. Auth boundary — Every cross-system call uses the target system's native auth (Shopify Admin token, Webflow CMS token, Xano API key, GA4 API secret, Adobe IMS OAuth, Resend API key).
  5. Tenant isolation — Config and credentials are scoped to tenant:{shop}:config. No cross-tenant reads.
  6. Audit trail — All consent mutations logged to consent_records (append-only). Adobe sync logged to adobe_sync_log.

3. Remaining Hardening Items

#ItemPriorityStatus
1Rate limit /auth/consent-syncMediumOpen
2Rate limit /auth/signup and /auth/login (brute force)MediumOpen
3Rate limit /auth/forgot-password (email enumeration)MediumOpen
4Add ADMIN_KEY secret if not already setCriticalVerify
5Rotate Shopify refresh tokens before 90-day expiryLowAutomated (cron)
6Verify Cloudflare Access policy covers all admin URLsLowVerify
7Add CSP headers to embed HTML responsesLowOpen
8Add X-Content-Type-Options: nosniff to all JSON responsesLowOpen