Create a Shopify Store-Aware Conversational Chatbot
Installing a Shopify custom app is easy. Making that install immediately useful is much harder.
Our goal was simple to describe but non-trivial to implement: the moment a merchant finishes installing our app, they should land in an AI chat that already "knows" their store—its identity, permissions, and (when allowed) key data like products and orders.
This article walks through the end-to-end engineering approach we took to make that happen, from OAuth to tenant mapping to AI orchestration and operational hardening.
1. Why We Built This
Our first version of the app did what many integrations do:
- The merchant installed the app.
- We completed OAuth and stored a token.
- They landed on a generic dashboard that asked for more configuration.
Technically, the install was a success. Practically, the merchant still had zero immediate AI value.
We wanted to change that. Our new target experience:
- Merchant clicks "Install" in Shopify.
- OAuth completes and we safely persist tokens and mapping.
- We bootstrap a session that understands their specific store (and only their store).
- The merchant lands directly in an AI chat that is ready to answer, "Show me my top products this week" or "Draft an email about the new collection" with real, store-scoped data.
Along the way we had to solve several constraints:
- Secure OAuth and HMAC validation in the Shopify model.
- Multi-tenant mapping of shops to internal tenants.
- Reliable token and session handling (retries, idempotency, uninstall handling).
- AI orchestration that could safely call Shopify APIs on behalf of the right store.
2. System Architecture at a Glance
We ended up with a layered architecture that separates concerns but keeps the install-to-chat path tight.
Core Components
- Shopify custom app: the app surface that merchants install from the Shopify App Store or as a custom app, plus any embedded UI.
- Installer / auth backend: handles OAuth dance, HMAC verification, token exchange, and mapping of
shopDomainto an internaltenantId. - Chat backend: manages chat sessions, user identities, routing, and persistence of messages.
- AI orchestration layer: wraps the model, prompt strategy, and server-side tooling that can read/write Shopify data.
- Data store: persists tenants, Shopify tokens, install status, and audit logs.
Trust Boundaries
We explicitly modeled the main trust boundaries:
- Browser ↔ App backend: browser is untrusted; all sensitive logic lives on the backend with CSRF protection and session controls.
- App backend ↔ Shopify APIs: authenticated via Admin API tokens; scopes are minimized.
- App backend ↔ AI service: we never send raw tokens or PII to the model; we send normalized, redacted data and strict instructions.
High-Level Flow
Merchant clicks "Install" in Shopify
→ Shopify redirects to our app's install endpoint with query params
→ We validate & redirect to Shopify OAuth authorization URL
→ Shopify calls our callback with authorization code
→ We exchange code for access token & verify
→ We create/update tenant mapping (shopDomain → tenantId)
→ We mint an app session & open a tenant-scoped chat
3. Shopify Install + OAuth Flow (Step-by-Step)
Shopify's OAuth flow is well-documented, but getting it production-ready and wired into AI chat requires attention to the small details.
3.1 Install Entrypoint
When a merchant installs the app, Shopify sends them to our install endpoint, for example:
GET /shopify/install?shop={shopDomain}&host={encodedHost}×tamp=...&hmac=...
At this point we:
- Verify required query params (
shop,hmac,timestamp). - Validate the HMAC signature against our app secret.
- Generate a state/nonce value and store it server-side (session or short-lived cache).
- Redirect to Shopify's OAuth authorization URL.
3.2 Authorization Redirect
We construct an authorization URL similar to:
https://{shopDomain}/admin/oauth/authorize
?client_id={apiKey}
&scope={scopes}
&redirect_uri={ourCallbackUrl}
&state={csrfToken}
&grant_options[]={offline|online}
Here, two decisions matter:
- Scopes: we requested only what we needed for chat use cases (for example, read_orders, read_products, write_draft_orders). Less scope means less risk.
- Token type: Shopify supports offline and online tokens.
We chose to use:
- Offline tokens for background tasks (e.g., precomputing embeddings, syncing catalogs).
- Online tokens when we needed to act on behalf of a specific admin user in real time.
3.3 Callback Handling & Token Exchange
After the merchant approves, Shopify calls our callback, e.g.:
GET /shopify/callback?code=...&shop={shopDomain}&state=...&hmac=...
Our callback logic performs several hard checks before ever creating a tenant or issuing an app session:
- Verify HMAC using the app secret and raw query params.
- Validate state/nonce to prevent CSRF attacks.
- Validate shopDomain (basic format and, if desired, allowed list).
- Exchange code for access token with Shopify's token endpoint.
- Persist or update the tenant/shop mapping and token (idempotently).
Examples of failure modes we explicitly handled:
- Invalid state: we return an error and do not create or modify any tenant records.
- Missing shop param: we log, show a generic error, and halt.
- Duplicate callback: we treat the callback as idempotent using a combination of
shopDomainand a unique install identifier. - Stale install: callbacks older than a small time window are rejected and logged.
3.4 Embedded App Context
If you are building an embedded Shopify app, some nuances apply:
- Shopify loads your app in an iframe inside the admin; you need to handle
hostandsessionparameters. - You may use Shopify App Bridge or other official tooling to manage redirects and session tokens.
- We still keep all sensitive OAuth handling on our backend; the browser only sees short-lived, app-specific tokens.
4. Tenant/Shop Mapping: The Real Foundation
OAuth is table stakes. The core design decision is actually your tenant/shop mapping model.
We treat each Shopify store as a tenant inside our system. The key identifiers we persist:
shopDomain(unique per Shopify store, e.g.example-shop.myshopify.com)shopifyAccessToken(offline and/or online, encrypted at rest)tenantId(our internal UUID for this store)installStatusand timestamps (installed, uninstalled, reinstalled, etc.)createdAt,updatedAt,lastSeenAt
Data Model Example
Tenant {
tenantId: uuid,
shopDomain: string, // unique
shopifyAccessToken: string // encrypted
installStatus: enum, // INSTALLED | UNINSTALLED | PENDING
installedAt: datetime,
uninstalledAt: datetime | null,
lastSeenAt: datetime | null
}
Idempotent Install Logic
Install and callback handling must be safe to retry. Our strategy:
- Use
shopDomainas the natural key. - If a record exists, update the token and timestamps instead of creating a duplicate.
- Wrap the mapping + token save in a transaction with an idempotency key derived from the callback.
- Log a correlation ID across the entire install flow for debugging.
Reinstall and Uninstall Behavior
We subscribed to the app/uninstalled webhook to handle lifecycle events:
- On uninstall, we mark the tenant as
UNINSTALLED, revoke or invalidate tokens, and clean up cached data. - On reinstall, we reuse the same
tenantIdfor continuity but generate a new access token and refreshinstallStatus.
Critically, we enforce strict cross-tenant isolation. Every request that touches Shopify APIs goes through a resolution step:
- Resolve the request's app session to a
tenantId. - Fetch the tenant's
shopDomainand tokens from storage. - Ensure the AI tooling call can only operate on that tenant's store.
5. Handing Shopify Context Into AI Chat
Once the install is complete and the tenant is created, we immediately bootstrap a chat that is scoped to that store.
5.1 Bootstrap Flow After Install
- Resolve shopDomain → tenantId The callback handler looks up or creates the tenant and captures its
tenantId. - Mint an app session / auth token We issue a first-party session token (e.g., JWT or opaque token) bound to that
tenantIdand the current admin user. - Redirect into chat UI The merchant lands on our chat frontend with a valid session token.
- Chat backend loads tenant-scoped context The chat backend calls our AI orchestration layer with a context package for that tenant.
5.2 The Tenant Context Package
When we call the AI orchestration layer, we do not just send the user message. We send a structured context bundle, for example:
{
"tenantId": "...",
"store": {
"shopDomain": "example-shop.myshopify.com",
"displayName": "Example Shop",
"timezone": "America/New_York"
},
"capabilities": [
"READ_PRODUCTS",
"READ_ORDERS",
"CREATE_DRAFT_ORDERS"
],
"flags": {
"canUseCustomerData": false,
"betaFeatures": ["ai_email_drafts"]
}
}
This gives the model clear boundaries on what it can and cannot do for that tenant.
5.3 Guardrails and Privacy
We added several guardrails:
- No cross-tenant context: the orchestration layer never mixes data across tenants. Every tool call requires a
tenantId. - Redaction of sensitive fields: for example, we avoid sending full customer emails or addresses to the model unless explicitly required and permitted.
- Server-side authorization before executing tools: even if a prompt asks for "all customers", our server enforces capabilities and scopes.
6. AI Workflow Design
We use a tooling pattern where the model does not call Shopify directly. Instead, it asks our server for data, and the server talks to Shopify.
6.1 Prompt Layering
Each AI request is composed of several layers:
- System instructions Define the model's role, tone, and hard guardrails. For example, never hallucinate orders; if unsure, ask to fetch data via tools.
- Tenant/store metadata Derived from the context package: store name, timezone, capabilities, feature flags.
- User intent The actual user message ("Draft a marketing email for my top 3 products this week").
6.2 Tooling Pattern
We expose server-side tools to the model such as:
list_products({ limit, sort })get_top_selling_products({ window })create_draft_order({ lineItems, note })
The typical sequence:
- Model decides it needs data and calls a tool, e.g.
get_top_selling_products({ window: '7d' }). - Our server receives the tool call and resolves the
tenantIdfrom the chat session. - The server calls the Shopify Admin API using the tenant's access token.
- We normalize and optionally redact the result.
- We pass that data back to the model as tool output.
- The model uses that trusted data to craft a response.
6.3 Why Server-Side Fetching Beats Client-Side Calls
We deliberately avoid client-side calls from the browser directly to Shopify for several reasons:
- Security: access tokens never leave our backend.
- Consistency: we centralize rate limiting, retries, and error handling.
- Auditability: every tool call and Shopify API request can be logged with a correlation ID.
- Guardrails: the model cannot bypass server-side checks, even if a prompt tries.
7. Reliability and Operational Hardening
A beautiful architecture is useless if installs fail or chats intermittently break. We invested early in reliability primitives.
7.1 Retry Strategy for OAuth & Callbacks
Network calls to Shopify (especially token exchange) can fail transiently. Our approach:
- Use exponential backoff with jitter for the token exchange request.
- Retry only idempotent operations (e.g., reading tenant, saving token with upsert semantics).
- Never retry blindly on validation failures like bad HMAC or invalid state.
7.2 Idempotency Keys
For critical paths like the install callback, we use idempotency keys (e.g., a hash of shopDomain + timestamp or a dedicated installId):
- If a request with the same key is reprocessed, we return the existing result.
- We store the status (success/failure) and correlation ID with that key.
7.3 Observability and Correlation IDs
Every install and chat bootstrap request gets a correlation ID that we propagate across:
- Install entrypoint
- OAuth callback handler
- Tenant mapping creation/update
- Chat session bootstrap
- First AI request and Shopify tool calls
We emit structured logs with fields like tenantId, shopDomain, correlationId, and eventType (INSTALL_STARTED, TOKEN_EXCHANGED, CHAT_BOOTSTRAPPED, etc.).
On top of logs, we track metrics such as:
- Install success rate
- Callback error rate (HMAC failures, state mismatches, token exchange errors)
- Time from install start to first successful chat message
7.4 Webhook Considerations
We treat webhooks as first-class citizens:
- app/uninstalled: mark tenant as uninstalled, revoke tokens, and disable AI tools for that tenant.
- Optional domain or store updates: keep metadata fresh but never change
tenantIdfor a store.
8. Security Decisions We Made
Security decisions are not just about compliance; they also reduce debugging pain and production surprises.
8.1 HMAC Verification
We implemented HMAC verification consistently for:
- The initial install request.
- The OAuth callback.
- Webhooks (where Shopify signs the body, not query params).
Key practices:
- Use the exact algorithm Shopify specifies (HMAC-SHA256) with the app secret.
- Sort and encode query params precisely as documented before hashing.
- Use constant-time comparison when checking expected vs. received HMAC.
8.2 CSRF & State Handling
Every OAuth authorization redirect includes a state parameter that we store and validate. If the state does not match on callback, we abort the flow and log the incident.
8.3 Token Storage & Encryption
Access tokens are stored only on the server side:
- Encrypted at rest using our platform's KMS or a dedicated secrets solution.
- Never exposed to the browser, logs, or analytics systems.
- Access controlled by strict service-to-service permissions.
8.4 Scope Minimization and Least Privilege
We started from the minimal set of scopes necessary for our chat use cases and added only when a clear, approved feature required it. This also made our AI tooling surface simpler.
8.5 Session Expiration and Rotation
App sessions for admins:
- Have a short, sliding expiration.
- Can be invalidated if uninstall or token revocation occurs.
- Are rotated periodically to reduce risk of token reuse.
9. Developer Experience & Debugging Playbook
Running a multi-tenant Shopify + AI app in production is as much about DX as it is about architecture. We built a small playbook for ourselves.
9.1 Local Testing Setup
- Expose local callbacks via a tunneling tool (e.g., ngrok or similar) to receive Shopify redirects and webhooks.
- Maintain separate app configurations for development, staging, and production with different API keys and redirect URLs.
- Use seeded test stores with realistic but non-sensitive data.
9.2 Known Symptoms → Likely Cause
Symptom Likely Cause Merchant sees "invalid signature" during install HMAC verification bug (parameter encoding or missing fields) Install completes, but chat page shows unauthorized Tenant not created or session not bound to tenantId during callback Chat works, but AI replies with "I cannot access your data" Missing Admin API scopes or capability flags not set in context Occasional 500s on install callback Token exchange not wrapped in retries / idempotent storage
9.3 Checklist Before Shipping to Production
- [ ] HMAC verification tests for install, callback, and webhooks.
- [ ] End-to-end install flow tested on at least one real dev store.
- [ ] Tenant/shop mapping is unique on
shopDomainand idempotent. - [ ] Correlation IDs show a full trace from install to first chat message.
- [ ] Metrics and alerts configured for install failures and token errors.
- [ ] AI tools enforce tenant scoping and capability checks on the server.
10. Results and Lessons Learned
After rolling out this end-to-end pattern, we observed several improvements:
- Faster time-to-value: merchants could send their first meaningful, store-aware chat message immediately after install.
- Reduced support load: fewer "install succeeded but nothing works" tickets.
- Safer experimentation: with strong tenant isolation and observability, we could add new AI tools without compromising security.
What We Would Do Differently
- Design the tenant mapping and AI capability model even earlier, before writing any UI code.
- Invest in a dedicated "sandbox" tenant mode to test new AI tools safely.
- Standardize a small internal library for HMAC, state handling, and correlation IDs from day one.
Recommendations If You Are Building This
- Treat tenant/shop mapping as a first-class subsystem, not an afterthought attached to OAuth.
- Centralize AI tooling on the server with strict tenant-scoped APIs; never expose Shopify tokens to the client or the model.
- Make installs observable with correlation IDs and metrics so you can diagnose failures quickly.
- Start with minimal scopes and expand only when a concrete feature demands it.
- Bootstrap chat immediately after install so merchants feel value the moment they authorize your app.
Tools We Used (At a Glance)
Integration & Infrastructure Shopify OAuth (Admin API authorization flow) Shopify Admin API (store-scoped data and actions for AI tools) Shopify app scopes and token model (offline/online as needed) App uninstall webhook for cleanup lifecycle Internal tenant mapping service + persistent datastore AI chat orchestration layer with server-side tool execution Structured logging and monitoring for install-to-chat traceability
If you are designing a Shopify + AI integration today, focusing on install → tenant map → context-rich chat as a single coherent flow will pay dividends in both user experience and operational sanity.
