App proxy
Pages of yours on the shop's own address, at /apps/your-slug, drawn inside the shop.
An app proxy gives your app pages on every shop that installs it, at the shop's own address:
https://<shop>/apps/<your-slug>/<path>?<query>A wishlist, a size quiz, a store locator or a form to ask about a product can live there, under the shop's header and above its footer, with no second domain for the shopper to trust.
[app_proxy]
url = "https://wishlist.example.com/proxy"A request for /apps/wishlist/saved?page=2 is forwarded to https://wishlist.example.com/proxy/saved?page=2. A path that tries to climb out of your folder, with .. however it is spelled, is refused before it reaches you.
What you receive
A GET, or the POST of a form on your page, signed as the kind proxy with the installation's secret. It carries these headers:
| Header | What it holds |
|---|---|
storeware-proxy | What was signed: the method, path, query, shop, path prefix and store, and a hash of the body |
storeware-signature | The signature over storeware-proxy |
x-storeware-shop-domain | The shop's hostname the shopper used |
x-storeware-path-prefix | /apps/<your-slug>, for building links and form actions back to yourself |
x-storeware-store-id | The store |
It never carries the shopper's cookies, their authorization or anything that says who they are. Your app learns which shop, which page and what the form said, and nothing about the person asking. Keep anything per shopper in the form itself, or ask them for it.
A form body is a browser's: application/x-www-form-urlencoded or multipart/form-data, at most 64 KB. Anything else is not forwarded.
Verifying a request
import { verifyProxyRequest } from '@storeware/plugin-sdk/proxy';
const { method, path, query, shop, pathPrefix, storeId } = await verifyProxyRequest({
secret: installation.secret,
proxyHeader: request.headers.get('storeware-proxy'),
signatureHeader: request.headers.get('storeware-signature'),
rawBody: await request.arrayBuffer(),
});It checks the signature, then checks the body against the hash that was signed, and throws WebhookVerificationError if either is wrong. Route on the path and query it hands back, which are what Storeware signed, rather than on the address your server saw. Pass the body as bytes: a multipart form may hold bytes that are not text.
What you can answer with
You have 3 seconds and 256 KB.
| You answer | The shopper sees |
|---|---|
text/html | Your page, inside the shop's layout |
application/vnd.storeware.components+json | Your components, inside the shop's layout |
application/json | Your JSON, as you sent it, for a script of yours to read |
| A redirect to a path on the shop, or to an address on the shop's own hostname | That redirect, with your status |
| 404 | The shop's own page for something not found |
| Anything else, or nothing in time | A page in the shop's style saying it is not answering, with a 502 |
Your own error pages never reach a shopper, and nor does a redirect off the shop.
HTML is held to the same allow list as a block, with one addition: a form, when it posts back to your own pages. Its action must be under /apps/<your-slug>, its method GET or POST, and its fields plain ones: input, select, option, textarea, button and label. A form that would post anywhere else is drawn as inert fields, and a password field is never kept. Wrap a field in its label rather than linking them with id, which is removed so it cannot collide with the shop's own.
<form action="/apps/wishlist/save" method="post">
<label>Email <input type="email" name="email" required /></label>
<button>Tell me when it is back</button>
</form>Every page carries the line naming your app, as a block does.
Not stored, not indexed
Nothing a proxy page answers is cached, by the shop or by its edge: every answer goes out no-store, because your page may differ for every request. Every page is also sent noindex for now, in a header and in the page, so search engines do not list it.
The pages are read from the release the shop has approved. A proxy a new release adds answers once the merchant approves that release, and a shop that removes your app answers its own not found page at once.