Organization DepotDevelopers
All pages

Security

What's safe in your page, the Content-Security-Policy your site needs, how to pin a release with Subresource Integrity, and how to keep pages fast.

What's in your page, and what isn't

  • A public token is safe to publish. It reads what it's for and nothing else; see Public tokens. There is no secret key to keep: nothing built on these scripts needs one.
  • A preview token never goes on your live site. See Preview tokens below.
  • Never put a token in a URL. Send it in the X-OD-Storefront-Token header (the scripts do). Query strings end up in server logs, analytics and Referer headers.
  • Organization Depot's text is text. Set titles, descriptions, details and personalization with textContent, never innerHTML. The web components already do.

Preview tokens

A preview token (odpv_…) is for building your site, and stays in your local settings:

  • It can't take money or see anything private. Like a public token, it reads the catalog and builds carts, and its carts pay only in test mode, so a leaked one can't cost you an order, take real money or reveal a buyer.
  • It still doesn't belong on your live site. It makes carts while the shop is closed, so buyers there could fill a cart for a sale that hasn't opened, then reach a checkout in test mode that takes no real card. Deploy your public token (odpk_…) and keep the preview token in a config file or .env your deploy leaves out.
  • Revoke one you no longer use on the Headless page, as you would a public token.

Content-Security-Policy

If your site sends a Content-Security-Policy, it needs three sources:

script-src https://developers.organizationdepot.com;
connect-src https://api.organizationdepot.com;
img-src https://api.organizationdepot.com;
Directive Why
script-src The scripts come from this site
connect-src The scripts call the API with fetch
img-src Product photos and logos are served from the API's host

Add them to what your site already allows. A complete policy for a site that serves its own files and nothing else could be:

Content-Security-Policy: default-src 'self'; script-src 'self' https://developers.organizationdepot.com; connect-src 'self' https://api.organizationdepot.com; img-src 'self' https://api.organizationdepot.com; style-src 'self'; object-src 'none'; base-uri 'self'
  • The web components need nothing else. Their styles are constructable stylesheets, which style-src doesn't govern, and they use no inline style attributes, inline scripts or eval.
  • Inline scripts in your own pages (like the short examples in the quickstarts) need a nonce or a hash, or move them into a file on your site, as the starters do.
  • No frame-src is needed. The Storefront's checkout is a full-page navigation to your checkout domain, never an iframe.
  • Testing against a local copy? Add http://localhost:4600 and your local API to the same directives; see Local development.

Pinning a version with Subresource Integrity

/storefront/v1/ always serves the latest 1.x release, so its files change. To control exactly which code runs on your site, pin a release, /storefront/1.3.0/, whose files never change, and add the browser's integrity check. The hashes are in each release's integrity.json:

https://developers.organizationdepot.com/storefront/1.3.0/integrity.json

For the Storefront's 1.3.0:

<script
  src="https://developers.organizationdepot.com/storefront/1.3.0/storefront.js"
  integrity="sha384-NYwf0zgpe0RTfmkrixkfB9QwymDMhCD/unlDZs134wJaHAqrBZUEmRudlfyaggOy"
  crossorigin="anonymous"
></script>

<script
  type="module"
  src="https://developers.organizationdepot.com/storefront/1.3.0/components.js"
  integrity="sha384-b37FmrVEYamX7LKBb/uns/KTY0FnjxqhyQrqruUYVpB6FHxLthSx2neAiCIAwUYt"
  crossorigin="anonymous"
></script>
  • crossorigin="anonymous" is required with integrity on a script from another origin; the script URLs answer every request with Access-Control-Allow-Origin: *.

  • Only pin with integrity. An integrity on a /storefront/v1/ URL breaks your site at the next release, when the file changes.

  • storefront.mjs. An import statement can't carry a hash, so preload the module with one, before your own module script:

    <link
      rel="modulepreload"
      href="https://developers.organizationdepot.com/storefront/1.3.0/storefront.mjs"
      integrity="sha384-F+kEgYq7PR5wJvRUsy9hGprMjpcMQUccxMA3GMvOIxgBL4IOUeafl85BeAOBlnXq"
      crossorigin="anonymous"
    />
    <script type="module" src="/app.js"></script>
    

    A browser that supports modulepreload checks the hash before the module runs, and your import of the same URL uses the checked module. With a wrong hash the module is blocked and your import fails. The href must be exactly the URL you import. A browser without modulepreload (Safari before 17, Firefox before 115) ignores the link, and its import runs the module unchecked. This suits a bundler that leaves the SDK external.

    The vite-ts starter adds the link itself when scriptBase is a release. It reads the hash from the release's integrity.json when you build, so it catches a file that changes after your build, not one that was already wrong then.

    The other ways are a <script type="module" integrity> tag, an import map's integrity field where your visitors' browsers support it, or a downloaded copy bundled into your build.

  • You upgrade by hand. A pinned site gets no fixes until you change the version and the hashes. Watch the Storefront changelog.

Performance

  • Cached at the edge and in the browser. Pinned files (/storefront/1.3.0/…) are cached for a year and never change. /storefront/v1/… is cached for 5 minutes, so a new release reaches your visitors within minutes.

  • Connect early. A hint in your <head> saves a round trip on the first request:

    <link rel="preconnect" href="https://developers.organizationdepot.com" crossorigin />
    <link rel="preconnect" href="https://api.organizationdepot.com" crossorigin />
    

    crossorigin matches how the files are fetched: the API's requests and module scripts (and any script with integrity) use CORS without credentials. For a plain classic <script src>, drop it from the first hint.

  • Mind the rate limits. About 120 reads and 60 writes a minute per buyer is far more than a person needs, but polling (re-reading on a timer, say) can reach it. A server that renders pages for many buyers is one client address: cache what it reads.

The Storefront

  • The cart id is the buyer's key. Anyone with it can read and change that cart. Don't log it, send it to analytics or put it in a link. The only URL it appears in is the checkout handoff, and the checkout takes it out of the address bar as soon as it has looked the cart up, whether or not the link is valid.
  • Card details never touch your site. Payment happens on the hosted checkout, so your pages don't need to meet card-data rules for it.
  • Small scripts. storefront.js is about 3.3 kB gzipped; components.js, with storefront.js inside, about 19.3 kB. Load one or the other, not both, unless you use both on the page.
  • Right-size photos. Use imageUrl(image, "small") for thumbnails and cart lines and "medium" for cards and product pages; keep "original" for zoom. Add loading="lazy" below the fold.
  • One client per page. It serializes cart calls and keeps one cart; the web components share one per token.
  • Listen, don't poll. Listen to cart:change instead of re-reading the cart.
  • Search engines. Products rendered in the browser are visible to search engines that run scripts. For plain HTML, render the catalog on your server with storefront.mjs or the HTTP API; see On a server.