Storeware Docs

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:

HeaderWhat it holds
storeware-proxyWhat was signed: the method, path, query, shop, path prefix and store, and a hash of the body
storeware-signatureThe signature over storeware-proxy
x-storeware-shop-domainThe 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-idThe 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 answerThe shopper sees
text/htmlYour page, inside the shop's layout
application/vnd.storeware.components+jsonYour components, inside the shop's layout
application/jsonYour 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 hostnameThat redirect, with your status
404The shop's own page for something not found
Anything else, or nothing in timeA 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.

On this page