Demo
A recorded session, not a live one.
The storefront engine answers inside a signed Shopify request, so what this page can hand you is the recording of 13 exchanges with that engine, taken straight from the repository. All 13 are replayed against the code as it stands today and compared byte for byte, which is what keeps the transcript below tied to the engine it describes.
Four conditions the engine checks before it answers.
A chat box on this page would have to satisfy all four, and each of them is load-bearing. This is the order the code checks them in, and the answer each one gives when its condition is unmet.
-
The request carries Shopify’s signature
The store comes out of the signature on a Shopify app proxy request, and that signature is the single source for whose catalogue the engine may open. An unsigned request is answered
401before a single line of the conversation code runs; the reason goes to our log and the caller gets the code alone, so an attacker learns that the request failed and stops there. -
The store resolves to an installed tenant
A signed request is matched to a store record with a usable sector package; anything else is answered
403. The code holds that line even where the package is missing, because a default rule set would answer an unplaced store with the wrong rules, and wrong rules are as expensive as a wrong catalogue. -
The maintenance gate is open
An open maintenance window — for that store, or for everyone — is answered
503with a retry header and the notice text, ahead of any model call and any catalogue read. Where the maintenance record itself is unreadable the storefront stays open, and the two data-deletion endpoints pass this gate regardless: a shopper’s right to be deleted outlasts a maintenance window. -
The store is on a plan, or inside its trial
Installed, with a subscription and a trial both absent, is answered
403— and that answer is the whole of it: the request stops at the gate, ahead of the model, ahead of the catalogue read, ahead of the counter. Every plan is a paid plan, so the trial is the door that opens first.
This codebase carries a second, public demo back end, written for the site that is live today, with its own spend ceiling, session cap and per-address rate limit. Wiring it to this page is a decision someone has to take: its bot check is served from another domain, and this directory serves every asset from its own. Until that decision is taken, the recording is what the page shows, and the page says so.
Thirteen exchanges, grouped by what came back.
Both halves are files in the repository. What was sent comes from the scenario list; what came back comes from the recorded copy, captured once against a real worker with a real database and a real signature, before the channel layer under it was rewritten. Every line below comes out of those two files.
It answered 3
-
200 · json
{"mesaj":"which shampoo for dry hair?"}For dry hair, a gentle moisturizing shampoo is a good place to start.
-
200 · stream
accept: text/event-stream, same messageThree events, in this order:
basladicarrying the session id,metincarrying the same sentence,bitticarrying a timestamp. -
200 · json
Same message, session id omitted
Same sentence, plus a session id the server generated.
It handed over 3
-
200 · json
Same message, on a store whose conversation cap was full
I’ve passed your message to our team, and a team member will follow up with you as soon as they can. If it’s urgent, you can also email us at {eposta}.
-
200 · stream
Same, over the stream
Four events:
basladi, thendevircarrying{"hedef":"eposta"}, thenmetin, thenbitti. -
200 · json
{"mesaj":"thanks!"}, after a teammate had taken the conversation overEmpty. The reply text is an empty string, with the conversation flagged as in human hands.
It broke, and said so 2
-
200 · json
Same message, with the model provider returning an error
Something went wrong on my side just now. Mind trying again in a moment? If it’s urgent, email us at {eposta}.
-
200 · stream
Same, over the stream
The
metinevent carries the same apology and the same error flag, thenbitticloses the stream cleanly.
It refused the request 5
-
405
GETinstead ofPOSTPOST bekleniyor
-
400
{bozuk— a body that is not valid JSONgeçersiz gövde
-
400
{"mesaj":" "}— whitespace onlymesaj boş
-
400
{"mesaj":42}— a number where a message goesmesaj boş
-
413
A message of 2,001 characters
mesaj çok uzun
All five of those bodies are in Turkish, and they are read by developers rather than shoppers: they are the engine talking to the storefront widget, which carries its own wording in every language it ships with. These strings exist so that a log reader can tell the five refusals apart. The five also share a property worth more than their wording — all of them stop ahead of the model, so a malformed request is free.
What the recording pins down, and where the rest is shown.
A recording proves whatever it happens to contain, and this one was made to pin down an interface. Five things it fixes in place, each with the place in the product where you read the other half of it.
The plumbing, rather than the sentence
In the recording the model provider is replaced by a stub that returns one fixed sentence, on purpose: that is what makes the same request produce the same bytes on every build. What the model itself would write is a thing you read against your own catalogue, in step three of setup.
The interface, rather than the tool calls
All thirteen exchanges carry an empty tool list, because a stub that returns plain text answers on its own. Every tool that runs in a real turn — a stock read, a price, a policy, an order — appears in the console inbox with its result and its timing.
The payload shape, rather than a product card
The product block came back empty in all thirteen, so the transcript shows the field and leaves it at that. The sample conversation on the home page does carry cards, and labels them as examples where they appear.
One store’s settings, rather than its catalogue
Three settings were resolved to make the recording: the store’s language, its support address, its conversation cap. Which store it was stays out of the transcript, and so does everything in its catalogue.
The bytes, rather than the clock
The recording carries exactly one timestamp and the comparison masks it, because a recording that changed on every run could prove only that it had changed. Speed is a thing you measure against your own store and your own traffic, so it belongs to the console.
What the recording does prove is narrow and worth having: every status code, every header and every byte of every body above still comes back from the code as it is written today. The comparison runs with the network cut off inside the process, so it stands on the code alone; a scenario that reaches for the internet fails on the spot.
Where you read the rest: your own console.
The tool calls, the sentences the checks held back and the quality of an answer are all things you judge against your own catalogue. Two places in the product put them in front of you, both of them behind the install.
Every turn, in the inbox
- The shopper’s question and the answer, as they were shown.
- Each tool that ran between them, by name, with its result and how long it took.
- Any sentence the checks held back, listed against the turn it came from.
- Whether the conversation was handed over, and where it went.
Before you go live, in setup
- The trial step of setup asks your own store a question with every side effect switched off.
- It lists what it would have done live — the return it would have opened, the note it would have written.
- The isolation is structural: everything with a side effect travels through one interface, and the trial hands that interface a stand-in.
- Where a turn would have left your store untouched, it says that too.