Measured
Zero lines of theme-writing code
A test walks the codebase for an Asset API call and for a script tag, in every state: live, disabled, and behind a flag. The count is zero in all three, which is why the app has no path to a theme file.
Security
Every access scope, header and data-deletion path Refik has, read from the code that ships. Every certification claim on this page is a denial, and the list of what we hold is further down.
Access
These are the exact permissions Shopify asks you to approve when you install Refik. The app configuration and the server that uses those permissions declare the same six, in the same words — a test compares the two lists and fails if they ever drift apart.
Your catalogue: titles, variants, options, prices and the product copy an answer is built from. This is the only source for “does this exist, and what is it.”
Whether a variant can actually be bought right now. Refik recommends only what your store can sell today, and this scope is where that answer comes from.
Order and shipping status — opened only when a shopper asks about their own order, and only after Shopify has proved who they are.
Opening a return request the shopper asked for. It cannot approve one, cannot refund one, and cannot change what your return policy says.
One thing, and one thing only: a refik/profil metafield holding the preferences a shopper stated about themselves, with a timestamp. No other customer field is ever written.
Reading your own shipping and return policy pages during setup, so a policy answer quotes your text. You review the draft before it goes live.
Each of the six has a real call in production, which is the reason it appears on the list at all. And there is no hidden seventh: adding one forces Shopify to show every installed store a fresh approval screen, which is exactly the point of the list.
Absence
That is what makes the list above a boundary, held by Shopify’s own approval screen. Below it: the two things Refik writes, and the permissions merchants ask about most, each one absent from that list by name.
Everything else Refik does with your store is a read.
Two more absences worth writing down, because both were measured. No query anywhere in the server asks for a shopper’s first name, last name, phone number or address — the only such words in the codebase are two instructions telling the assistant to leave them alone. And there is no analytics pixel and no cookie of ours on your storefront: when a conversation turns into an order, Refik reads that from Shopify’s own order record, because the chat wrote a hidden cart attribute and the attribute survived into the order. Card and payment data stay with Shopify, out of reach of every app.
Your theme
Every storefront surface ships as one theme app extension with five blocks. You switch each one on inside Shopify’s own theme editor; Refik hands you a deep link to the right panel and then stays out of it.
Measured
A test walks the codebase for an Asset API call and for a script tag, in every state: live, disabled, and behind a flag. The count is zero in all three, which is why the app has no path to a theme file.
Platform rule
App Store requirement 5.1.1 says an app must leave theme code alone, and Shopify audits for it. Listing is conditional on that, so this is a claim a third party checks.
On removal
The surfaces belong to the extension, so they go when the app goes. Your theme is byte for byte what it was before you installed, and the cleanup list is empty.
Proof of identity
Three different surfaces talk to Refik, and each one is verified differently. This is the part of the system everything else rests on, so it is worth being specific about.
Storefront
Chat requests arrive through Shopify’s App Proxy, signed with our client secret. The signature decides which store is asking, and a signed logged_in_customer_id is what proves who the shopper is. Order, shipping and return data opens on that field and nothing else.
A guest can still look up their own order with an order number and the matching e-mail. Order numbers are sequential, so that path is rate limited: five attempts per chat session, then it closes.
Webhooks
Shopify’s webhooks arrive outside App Proxy, so the one gate is a signature over the raw body. Parsing waits for that signature: the raw text is read to compute it, and the JSON is parsed after it passes.
Which store a deletion applies to is taken from the signed body, never from the unsigned header — otherwise one shopper’s request could delete another store’s rows.
Admin console
Inside Shopify admin the console authenticates with the session token Shopify itself issues, exchanged for a short-lived Admin API token. Your Shopify login is the only login, and the exchange happens server-side.
Teammates you invite sign in individually on the web console, so access is per person and can be removed per person.
At rest
The access token Shopify issues is the most valuable thing we hold, so it is the thing described in the most detail here.
AES-GCM with a 256-bit key, derived with SHA-256 from a secret that lives only in the server’s own secret store. Authenticated encryption, so a tampered value fails loudly where it would otherwise decrypt to something else.
At least thirty-two random bytes. A key that is too short, or malformed, is rejected outright — the server refuses to start that path, where the alternative is carrying on with a weak key.
A fresh random twelve-byte value for every single encryption. The same token encrypted twice produces two different strings, which the cipher requires.
A previous key can be set alongside the current one during a changeover; a row that still decrypts with the old key is re-encrypted with the new one as it is read. If the “old” key is identical to the current one the rotation is refused, because a key that was never actually changed would look rotated and would not be.
A wrong key or a corrupted value raises an error. It never quietly returns an empty string, because silent decryption failure looks exactly like a store with no token and would send the app down a repair path that repairs nothing.
The token itself expires: roughly an hour for the access token and about ninety days for the refresh token, and every refresh returns a new pair. A single stolen value is useful for that window and expires with it.
Deletion
Shopify forwards privacy requests to apps through three mandatory webhooks. Refik is subscribed to all three and acts on them within thirty days. Because your store is the controller of this data, a shopper sends their request to you, and you pass it on.
Data request
We assemble the record for that customer — saved routines and their dates, when they were first and last seen, how many conversation records exist, any back-in-stock requests, and any support handoff on file — so you can answer them.
Customer deletion
Saved routines, the first and last seen record, their conversation records, their back-in-stock requests and notification markers, their support handoff, and the preference metafield. Where a single chat session was used by two people, those few shared rows are left alone: deleting too little beats deleting someone else’s data.
Store deletion
Shopify sends this one two days after you remove the app. Every database row for your store goes: conversations, counters, feedback, handoffs, staff messages, saved routines, customer records, usage counts, settings and access tokens — plus the key-value records and the knowledge base built from your catalogue.
Database rows deliberately survive those two days: a merchant who uninstalls by accident and reinstalls the same afternoon keeps their conversation history and their knowledge base.
That edge is written down here because the code has exactly three paths, and a fourth one on a page would be a promise the code cannot keep.
One thing is kept on purpose: our own staff access log, for a thirteen-month security retention. It records which of us looked at what, and investigations reach back further than a month. It is a record about us, which is why a shopper deletion leaves it standing.
Sub-processors
Four companies process data on our behalf. A disclosure needs the purpose as well as the name, so each row below says what that company does and what leaves our system to reach it.
Hosting, database, key-value storage and logs — and Turnstile, the bot check on the free-form question box of our public demo.
Everything we store. For Turnstile specifically, the visitor’s IP address and a challenge token, sent for verification only; the raw address is never written to our own storage. With Turnstile unconfigured the free-form box is closed — the bot check is the condition for opening it.
The AI model that writes assistant replies and product summaries. Today’s provider for every store.
The conversation, the page the shopper is on, their saved preferences, and the order or tracking details a tool returned. A signed-in shopper’s account e-mail and customer ID are not put into the model’s context — the module that talks to the model never handles that identifier at all.
An alternative model provider doing the same job. A store can be switched to it; none has been, and the key is not configured in any environment.
The same categories as above, carried by the same code path. Your store’s domain stays here: what travels is a hash of it. It is listed because either provider may end up processing a store’s conversations, and this page is where you read that first.
The transactional e-mail the console needs: your sign-in link, team invitations, and alerts about setup, quota and a waiting handoff.
The recipient’s e-mail address and the contents of that message. The recipient is you or a teammate you invited, the purpose is transactional, and the address stays out of every log line of ours.
Klaviyo sits outside this list on purpose: back-in-stock events go to your Klaviyo account under your own agreement with them, and only where you configured one. And this list is a measurement: a test walks every origin the code talks to and fails if one of them is missing from the privacy policy. Adding a dependency fails it too, because a provider’s SDK can carry an origin that never appears as a literal address in our source.
Headers
The page you are reading and the app you install are different things served from different places, and one set of headers for both would flatter whichever is weaker. So here are both.
Nothing is allowed by default; scripts, styles, fonts and images may come only from this origin. Forms are switched off entirely, and no page may frame this one. Alongside that: strict transport security, no content-type sniffing, a referrer policy, and a permissions policy that turns off camera, microphone, geolocation, payment and topic-based advertising. There is no third-party request on this page at all — the fonts are in the repository, and the script makes no network call.
The console shell allows only its own origin. When Refik renders inside Shopify admin, the frame permission is set to your store’s domain plus Shopify’s admin domain and nothing else; if that value fails its format check it narrows to the admin domain alone. Account pages add no-store and no-referrer. Transport security is set on every https response.
The policy for this site is pinned by a test that compares it directive by directive, so loosening it in a deploy turns that test red. The same test measures the third-party origin count on this page, which is the claim above and the evidence for it at once.
Data protection
The practices behind the rest of this page, one at a time, with the gaps named where they exist.
The database’s recovery mechanism is Cloudflare D1’s Time Travel, which is always on and can restore the database to an earlier point in time within its retention window — seven days on the Cloudflare plan we are on today. The service makes no other copies of your data, and key-value storage has no point-in-time recovery. Cloudflare documents D1 data as encrypted at rest; we have no written confirmation that this covers recovery data, so we do not claim it.
Staging is a separate deployment with its own database and its own key-value storage, shared with production in neither. Its hourly job works only on the stores in the staging database and skips the built-in list of live stores. A separate Shopify app for staging has not been created yet, so that half of the separation is not claimed here.
Our internal admin panel sits behind Cloudflare Access, and our server verifies the Access token itself and then an explicit allow-list of addresses. If that configuration is missing the panel refuses every request, and no path to it runs through your storefront.
Every read our staff make through that panel is written to the access log before the data is read: who, what, which store and which conversation. Refused attempts are written too. The log is kept for thirteen months, a refused attempt for thirty days, and neither holds an IP address or a browser string. Direct database access through Cloudflare’s own dashboard is not in this log; it is limited to the members of the Cloudflare account that hosts the service.
Multi-factor authentication is required on every staff account that can reach customer data: Cloudflare, Shopify Partners and GitHub. This one is a policy we hold ourselves to; neither Shopify nor a test checks it.
The Shopify client secret, the token-encryption key and the AI provider keys are stored as Cloudflare secrets, set per environment. None of them is in the code or the repository, and the local secret files are excluded from version control.
E-mail addresses and phone numbers are masked in the stored record of which tools ran, before it is written. A signed-in shopper’s account e-mail and customer ID are kept out of the model’s context. The conversation text itself is stored as written, because your inbox needs it.
Shopper data leaves the system only through routes you start: the console’s conversation export, a CSV of up to 92 days and 5,000 rows per file with spreadsheet formulas neutralised, and a data request you send from Shopify admin. Our own panel’s exports carry store-level counts and costs, no shopper data, and each one writes an access-log entry.
An actual or suspected breach of merchant data is reported to Shopify no later than 24 hours from our becoming aware of it, without waiting for certainty or for a sub-processor’s notice. Affected merchants are told without undue delay. We contain first, preserve logs and evidence before any destructive step, and review every incident afterwards.
The same commitments, written as processor terms, are in the data processing addendum.
Honesty
A security page is the easiest place in a product to imply an audit nobody ran, so this section names the checker for each kind of claim above — and says plainly where the checker is us.
The six access scopes are the ones Shopify shows you at install, so the list above is one you approve in Shopify’s own words. And App Store requirement 5.1.1 is audited by Shopify, which puts the theme-writing claim in the same category: a third party gates it.
A test compares the app configuration’s scope list with the server’s. Another compares the site policy directive by directive. A third walks every origin the code talks to and fails when one of them is missing from the privacy policy. Each one turns red on drift, in the same run as the product tests.
We hold no SOC 2 report, no ISO 27001 certificate, and no PCI or HIPAA attestation. Procurement that requires one of those has its answer on this line, which is the reason the line is here and this high up the page.
Everything above that Shopify and those tests leave uncovered is our own reading of our own code.
Questions
No. The reason is the permission list itself. Refik holds read access for products, inventory and orders, so the ability is absent from what it was granted at install. Changing that would mean asking every installed store to approve a new permission.
A signed-in shopper’s account e-mail and customer ID are not placed in the model’s context; the module that talks to the model never handles that identifier. When a conversation is handed to your team, the model is told only that an e-mail is on file, not what it is. A shopper can of course type an address into the chat themselves, and a guest order lookup asks for one — in that case it is in the conversation because they wrote it there.
No. Every store is a separate tenant and the separation lives in the data layer: it is a query boundary, not a sentence in a prompt asking the model to behave. The distinction matters, because a prompt instruction is a request and a query boundary is a wall.
Anthropic, for every store, today. The code can route a store to OpenAI, and both are disclosed as sub-processors because either one may end up doing the work. Listing a provider before it processes anything is the point of the list.
There is no pixel, no cookie of ours, and no tracking script on your storefront. When a conversation turns into an order, Refik reads that from Shopify’s own order record: the chat writes a hidden cart attribute, the attribute stays on the order, and we read it back afterwards. That also makes the figure verifiable on the server, where a browser report would be the only alternative.
No. Each plan has an included conversation allowance and, on monthly plans, a capped overage band above it; once that absolute ceiling is reached, conversations stop and billing stops with them. Annual plans carry no overage band, so the included allowance is the hard stop. Every upgrade is one you make.
Something on this page unclear, or contradicted by what you see in your own admin? That is a bug in the page and we want to know. Every claim above was read out of the code, and a claim can be accurate and still be the wrong thing to say.