Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.stora.co/llms.txt

Use this file to discover all available pages before exploring further.

If you’re distributing an integration to many operators through code that runs outside infrastructure you control — for example a WordPress plugin, browser extension, client-side app extension, or packaged on-premise tool — you cannot embed OAuth 2.0 client credentials in that distributed code. This guide compares two patterns: the recommended broker pattern, and a lighter authorization proxy alternative for cases where you accept that Stora tokens will live in each plugin installation.

Choose an option

Use the broker pattern when:
  • Your integration is installed by each operator on infrastructure you don’t control (their WordPress site, browser, server, or laptop).
  • You need access to operator data beyond simple read-only, public information.
  • You want a single place to rotate credentials, revoke individual installations, and monitor usage.
Use the authorization proxy alternative only when:
  • You need to keep the Stora client_secret out of distributed plugin code, but you cannot operate a full broker that stores tokens and proxies every API call.
  • Your plugin can protect Stora access and refresh tokens on each operator’s infrastructure.
  • You accept weaker revocation, observability, and compromise isolation than the broker pattern provides.
You don’t need this pattern if your integration runs on infrastructure you control and operators authorise it directly — that’s a standard Authorization Code flow.
PatternStores Stora client_secretStores Stora access and refresh tokensResponsible for token security
BrokerYour brokerYour brokerYour backend infrastructure
Authorization proxyYour proxyEach plugin installationYour plugin and the operator’s infrastructure
A hosted Shopify app usually uses the standard Authorization Code flow because the app backend is operated by the app developer and can act as the confidential OAuth client. Do not put Stora credentials or tokens in Shopify theme code, app extensions, or other client-side/distributed code. Route those calls through a backend you host.
The broker pattern is the preferred option for public plugins. It keeps Stora tokens and OAuth credentials on infrastructure you control, and gives you one place to revoke installations, monitor usage, and handle refresh-token rotation.

Architecture

The broker pattern splits responsibility across three actors. Your distributed integration code runs inside an environment you don’t control — it holds no Stora credentials. Your broker is a backend service you host — it holds your Stora client_id and client_secret, stores each operator’s access and refresh tokens, and is the only thing Stora sees on the other end of OAuth. Stora issues exactly one confidential OAuth application to you, regardless of how many installations of your integration exist.

Connection flow at a glance

API call flow at a glance

Stora sees one client (your broker), one redirect URI, one pair of credentials. Operators see your plugin. This is what makes the pattern safe to distribute publicly — the credentials that prove identity to Stora never leave your servers.

Why the broker doesn’t hand Stora tokens to the plugin

It’s tempting to skip the broker’s own token layer and just forward Stora’s access_token and refresh_token to the plugin. Doing so re-creates the problems the broker exists to avoid:
  • Refresh-token theft persists. Stora’s refresh tokens are long-lived bearer credentials. Once exfiltrated from a plugin’s database or backup, they work until revoked.
  • You lose the kill switch. To cut off a single installation you’d have to revoke Stora tokens — which affects the operator’s other integrations and requires reconnection.
  • You lose scope narrowing. Broker-issued tokens can be narrower than the upstream OAuth grant. Raw Stora tokens cannot.
  • You lose observability. Direct plugin → Stora calls bypass your broker’s logs, metrics, and rate limiting.
  • Refresh rotation gets messy. Stora rotates refresh tokens on every use; with plugin-held tokens, every rotation has to be pushed back to each installation.
  • The client_secret is irrelevant after exfiltration. Attackers use stolen tokens directly — they don’t need to mint new ones.
If you decide to hand Stora tokens to the plugin anyway, use the authorization proxy alternative below rather than putting your client_secret in the plugin. Encrypt tokens at rest and be clear-eyed about what encryption buys you. See Alternatives considered for the full list of mitigations and their limits — the short version is that encryption at rest only meaningfully protects against DB dumps, not against RCE or a malicious sibling plugin.

Connecting an operator

Before you can make API calls on behalf of an operator, the operator’s installation needs to go through the Authorization Code flow once. All three of client_id, client_secret, and the redirect_uri belong to your broker — the plugin never sees them. The flow below shows what your broker implements.
This guide assumes Stora has already issued you a confidential OAuth 2.0 application with a single redirect_uri pointing at your broker (e.g. https://broker.yourcompany.com/stora/callback). Partner credentials are not self-serve today — we provision them for you during onboarding. See Building a partner integration for how to get set up.
1

Plugin starts the connection

The operator clicks “Connect to Stora” in your plugin’s UI. The plugin redirects the operator’s browser to your broker’s /connect endpoint, passing whatever you use to identify this installation (site URL, install ID, tenant slug).
GET https://broker.yourcompany.com/stora/connect
  ?install_id=shop.example.com
  &plugin_nonce=<install-time shared secret>
The plugin_nonce is yours to design — it lets the broker trust that this redirect actually came from a real installation of your plugin, not a random browser. See What your broker is responsible for for plugin-to-broker authentication notes.
2

Broker redirects to Stora

The broker mints a fresh state value, stores {state → install_id} in short-lived storage (Redis, or a DB row with a TTL — a few minutes is enough), and redirects the operator’s browser to Stora:
HTTP/1.1 302 Found
Location: https://app.stora.co/oauth2/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://broker.yourcompany.com/stora/callback
  &response_type=code
  &scope=public.contact:read public.order:read
  &state=<opaque, unguessable>
The state parameter is not optional for a broker: it prevents CSRF on the callback and lets you correlate the returning code with the right installation.
3

Operator approves in Stora

The operator logs in to their Stora BackOffice (if not already) and approves the requested scopes. Stora redirects back to your broker’s callback with a one-time code:
GET https://broker.yourcompany.com/stora/callback
  ?code=AUTHORIZATION_CODE
  &state=<echoed back>
4

Broker exchanges the code for tokens

Verify the state matches one you issued recently and recover the install_id. Then exchange the code at Stora’s token endpoint, authenticating with your client_secret:
curl -X POST "https://public-api.stora.co/oauth2/token" \
  -H "content-type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://broker.yourcompany.com/stora/callback"
Response:
{
  "access_token": "ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 7200,
  "scope": "public.contact:read public.order:read",
  "created_at": 1710000000,
  "refresh_token": "REFRESH_TOKEN"
}
5

Broker stores tokens and returns to the plugin

Store the access_token, refresh_token, and expires_at in your broker’s database, keyed by install_id. Issue a broker-scoped token (an opaque identifier you mint yourself) back to the plugin and redirect to the plugin’s success URL:
HTTP/1.1 302 Found
Location: https://shop.example.com/wp-admin/admin.php?page=your-plugin&broker_token=<opaque>
From this point on, the plugin holds only the broker token. Stora’s access_token and refresh_token live on your broker and never leave it.
The redirect_uri Stora sees on every connection is always https://broker.yourcompany.com/stora/callback — one URL, regardless of how many operator installations exist. That’s the property that makes this pattern safe to distribute publicly.

Making API calls

Once connected, the plugin makes requests to your broker, the broker translates them into Stora API calls using the stored access_token, and returns the response to the plugin. The broker is responsible for refreshing expired tokens silently and for surfacing a “reconnect required” signal when refresh fails.

The happy path

The plugin calls your broker. Authenticate the plugin with the broker-scoped token you issued during the connect flow:
curl -X GET "https://broker.yourcompany.com/stora/orders" \
  -H "authorization: Bearer BROKER_TOKEN"
The broker looks up the installation, checks the stored expires_at, refreshes if needed (see below), and proxies the request to Stora:
curl -X GET "https://public-api.stora.co/2025-09/orders" \
  -H "authorization: Bearer STORA_ACCESS_TOKEN"
Return the response to the plugin as-is, or reshape it to match your plugin’s data model — your choice.

Refreshing tokens

Stora access tokens expire after 2 hours. When the stored expires_at is within a small buffer of now (e.g. 5 minutes), refresh before making the call:
curl -X POST "https://public-api.stora.co/oauth2/token" \
  -H "content-type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=STORA_REFRESH_TOKEN"
The response contains a new access_token and a new refresh_token. The previous refresh token is revoked immediately. Your broker must atomically update both values, or the next refresh will fail.
Refresh-token rotation is not optional on Stora’s side — every successful refresh invalidates the previous refresh token. If your broker loses track of the latest refresh_token (crash between HTTP response and DB write, race between two parallel refresh attempts for the same installation), the installation is bricked until the operator reconnects. Serialise refresh attempts per installation and write the new token before returning the new access token to callers.

When refresh fails

A 400 from the Stora token endpoint with "error": "invalid_grant" means the grant is no longer valid — usually because the operator disconnected your integration in Stora BackOffice. You cannot recover this without the operator going through the connect flow again. The broker should: mark the installation’s tokens as revoked locally, return a well-defined error to the plugin (e.g. 401 Unauthorized with a body like {"error":"stora_reconnect_required"}), and let the plugin prompt the operator to reconnect.

Rate limits

Stora applies rate limits per operator (10 req/s, 60 req/min — see rate limiting). Your broker adds no rate-limit protection by default; a 429 from Stora propagates back to the plugin. Consider short request coalescing (same plugin, same endpoint, same operator, within the same second) if your plugin is chatty.

Disconnecting and reconnecting

Operators need to offboard cleanly, re-grant access after revocation, and occasionally switch which Stora account they’ve connected — without uninstalling the plugin. Your plugin’s settings UI must expose Disconnect and Reconnect actions, and the broker must back them with the logic below.

Disconnect

The plugin’s settings UI exposes a Disconnect button. When the operator clicks it, the plugin calls a disconnect endpoint on your broker — authenticated with the broker token — and your broker:
  1. Calls Stora’s POST /oauth2/revoke with the stored access token and your OAuth client credentials.
  2. Deletes the stored Stora tokens and the broker token for this installation.
  3. Returns 204 No Content to the plugin.
curl -X POST "https://public-api.stora.co/oauth2/revoke" \
  -H "content-type: application/json" \
  -d '{
    "token": "STORA_ACCESS_TOKEN",
    "grant_type": "client_credentials",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'
Stora stores the access token and refresh token on the same OAuth record, so revoking either string revokes the pair — one call is enough.
Broker routes such as /stora/connect, /stora/callback, and /stora/disconnect are illustrative throughout this guide. Your broker can expose whatever URLs you prefer; only the upstream Stora calls (/oauth2/authorize, /oauth2/token, /oauth2/revoke) are fixed.

Reconnect

The plugin’s settings UI also exposes a Reconnect button, which redirects the operator back through your existing connect entry point. The broker overwrites the installation’s stored tokens on the next successful callback — no new endpoint required. Expose Reconnect even while the current tokens are still valid. Operators use it to re-grant expanded scopes when your integration adds a new feature, or to switch to a different Stora account without a reinstall.

On plugin uninstall

The integration should fire the same disconnect flow automatically when the operator removes it from their host, using whatever uninstall or deprovisioning hook the platform exposes — for example WordPress’s register_uninstall_hook, or Shopify’s app/uninstalled webhook for hosted Shopify apps. Treat uninstall hooks as a hygiene layer, not a replacement for the explicit Disconnect action. Some platforms don’t fire them reliably: a user who deletes a WordPress site wholesale never triggers plugin uninstall hooks, and webhook delivery can fail on any hosted platform. The explicit Disconnect action plus Stora-side revocation remains the source of truth.

What your broker is responsible for

The broker is a piece of infrastructure you own and operate. At minimum, plan for the following.

Plugin-to-broker authentication

Stora authenticates your broker via client_secret. Your broker needs its own way to authenticate plugin installations. A common pattern: the plugin generates a long random value at activation time, registers it with the broker (HTTPS call keyed by install URL + admin email), and uses it as a bearer token on subsequent calls. Bind each token to one installation so a token stolen from one site can’t be used to impersonate another. HMAC-signing requests with a per-install secret is a stronger alternative.

Token storage and rotation

Store access_token, refresh_token, and expires_at per installation. Refresh tokens rotate on every use — the previous one is revoked the instant Stora returns a new one. Write the new refresh_token to your database before you return the response to whoever triggered the refresh, and serialise refresh attempts per installation so two parallel workers can’t race. Encrypt tokens at rest; the column on your broker’s database is as sensitive as your Stora client_secret.

Per-installation kill switch

Because the broker mints its own plugin-facing tokens, you can cut off a single installation without touching Stora: invalidate the broker token locally and the plugin starts receiving your reconnect error on every call. This is the fastest response to a compromised installation — no Stora support ticket, no scope re-negotiation, no impact on other installations.

Monitoring and abuse detection

Log every call that passes through the broker with install_id, endpoint, and status code. Basic alerts to set up: sustained 429 rates per installation (indicates a runaway plugin), sustained 4xx rates (indicates a broken installation or probing), sudden growth in call volume from a single installation, and anything that looks like credential-stuffing against your own /connect endpoint. Ship these to whatever you already use — you don’t need anything Stora-specific.

Disconnect and reconnect endpoints

Expose routes the plugin can call to trigger Disconnect (broker revokes tokens at Stora and wipes local state) and Reconnect (broker redirects back through the connect flow). Wire the same revoke-and-discard path into the plugin’s platform-specific uninstall hook as best-effort hygiene.
Operators never see your broker. To them, your plugin is the integration and Stora is the data source. Everything about the broker — its URL, its storage, its uptime — is your private implementation detail.

Option 2: Authorization proxy (lighter alternative)

The authorization proxy is a lighter alternative to the broker pattern. It keeps your Stora client_secret on infrastructure you control, but it does not keep Stora tokens out of the plugin. The plugin installation — for example a WordPress site — stores the operator’s Stora access_token and refresh_token, and calls the proxy whenever it needs to exchange an authorization code or refresh a token.
Prefer the broker pattern for production integrations that handle customer PII, payments, contracts, access control, or other sensitive data. The authorization proxy reduces deployment complexity, but every plugin installation becomes part of your token trust boundary. You are responsible for protecting those tokens on the operator’s infrastructure. WordPress environments vary widely, and tokens can be exposed through database dumps, backups, compromised admin accounts, vulnerable plugins, or server-level access. The authorization proxy protects your Stora client_secret, but it does not protect operator tokens after they are issued. If a refresh token is leaked, an attacker can mint access tokens until the grant is revoked, or until refresh-token rotation invalidates the stolen token before the attacker uses it. Choose the broker pattern if you do not want plugin installations to hold Stora tokens.

How the authorization proxy works

The plugin starts the OAuth connection by sending the operator to the authorization proxy. The proxy validates that the plugin callback URL is HTTPS and allowed for the installation, creates a signed state value containing the callback context, and redirects the operator to Stora. After approval, Stora redirects back to the proxy with the authorization code; the proxy verifies the signed state, creates a signed short-lived code_context bound to that code and installation, and redirects the browser back to the plugin with the code. The plugin then calls the proxy server-to-server to exchange the code for Stora access and refresh tokens. The server-to-server call must authenticate the plugin installation, for example with an Authorization header or HMAC signature. The plugin stores the returned tokens locally, but never calls Stora’s token endpoint directly. When the access token expires, the plugin sends the stored refresh token to the proxy; the proxy authenticates to Stora with the partner client_secret, performs the refresh, and returns the rotated tokens for the plugin to replace locally. The code_context is not a replacement for the Stora authorization code. It is a short-lived signed value created by the proxy so the proxy can stay stateless. It binds the authorization code to the plugin installation and callback that started the flow. When the plugin later calls /token, the proxy verifies the code_context before using its client_secret to exchange the code with Stora. You can implement code_context as a signed JWT/JWS or any equivalent authenticated token format. The payload should include the authorization code hash, installation identifier, callback URL, and short expiry. Sign it with a proxy-only secret. Do not put the Stora client_secret or access/refresh tokens in code_context.
{
  "code_hash": "sha256:BASE64URL_SHA256_OF_AUTHORIZATION_CODE",
  "install_id": "demo-self-storage.example.com",
  "callback_url": "https://demo-self-storage.example.com/wp-admin/admin.php?page=your-plugin",
  "expires_at": 1710000300
}
When /token is called, verify the signature, expiry, install_id, callback URL, and that sha256(code) matches code_hash before exchanging the code with Stora. The diagram below uses WordPress as the example plugin host.

Authorization proxy trade-offs

Compared with the broker pattern, the authorization proxy removes the need to store per-installation Stora tokens on your backend. That makes the proxy easier to operate, and it can be close to stateless if you use signed, expiring state and code_context values. The trade-off is that Stora tokens now live in the plugin installation. A compromised WordPress database, backup, admin account, server, or sibling plugin can expose the operator’s Stora tokens. You also lose the broker’s per-installation kill switch, API-level observability, rate limiting, and scope narrowing. Use this pattern only when that risk is acceptable, and document the token-storage responsibility clearly for operators.

Alternatives considered

You don’t need to read this section to build the integration. It exists for partners who want to understand why we recommend the broker pattern over the alternatives the OAuth 2.0 spec technically allows.
PKCE protects authorisation codes from interception during the redirect. It does not protect OAuth tokens after they’re issued. For a plugin distributed to many operators, the consequences are:
  • Tokens live on the operator’s infrastructure. wp_options is plaintext; other sensitive values in the same table get exfiltrated together in DB dumps, backups copied to staging, and compromised-plugin incidents.
  • No client authentication. A public client_id can be copied into any application. PKCE binds a code to a device but proves nothing about who the client is. A phishing app reusing your client_id presents the real Stora consent screen with your branding.
  • Refresh tokens are long-lived bearer credentials. Once out of the operator’s database they work until revoked. Rotation shrinks the window; it doesn’t close it.
  • Loose redirect URIs. Thousands of installations mean either wildcard redirect URIs (vulnerable to subdomain takeover) or Dynamic Client Registration (see below).
  • No abuse isolation. Revoking the public client_id breaks every installation simultaneously.
  • No consent phishing protection. An attacker can complete the flow with your client_id since there’s no secret to prove identity.
Public clients plus PKCE are designed for single-user applications on a user’s own device (RFC 8252 — native mobile and desktop apps). They are not designed for server-resident multi-tenant integrations where every installation stores its own long-lived tokens on infrastructure you don’t control. Hosted marketplace apps with developer-operated backends are different: the backend can keep credentials and tokens server-side, which is the same security boundary the broker pattern creates.
DCR would let each plugin installation register its own client_id (still public, still PKCE) at install time. Each site then has its own OAuth client record on Stora’s side, which solves the redirect-URI and abuse-isolation problems.Stora does not currently support Dynamic Client Registration. If you have a use case that specifically requires it, get in touch — we prioritise based on partner demand. DCR alone doesn’t solve the token-storage problem (tokens still live in wp_options); it mainly simplifies the consent and redirect-URI side of operating many public-client installations.
We understand the broker pattern adds operational overhead. If you decide to let the plugin store Stora access and refresh tokens, use the authorization proxy shape above. Do not put the Stora client_secret in the plugin, and do not have the plugin call Stora’s token endpoint directly. The plugin should receive the authorization code in its callback, then call your proxy to exchange the code or refresh token. Your proxy authenticates to Stora with the client_secret and returns the resulting tokens to the plugin.At minimum, do the following:
  • Keep the client_secret proxy-side. The proxy exists to protect the confidential OAuth client credentials. The plugin should only ever see the authorization code, code_context, access token, refresh token, expiry, and the plugin-to-proxy credential you design.
  • Use signed, expiring state and code_context. If you want the proxy to stay stateless, encode the callback URL, installation identifier, and nonce inside a signed state value. Validate the callback URL is HTTPS and allowlisted for the installation before redirecting. After Stora redirects back, issue a signed code_context bound to the authorization code hash, installation identifier, and short expiry. Reject expired, tampered, or mismatched values.
  • Authenticate plugin-to-proxy calls. The /token and /refresh endpoints still need plugin authentication, such as an Authorization header with an install-time shared secret or HMAC-signed requests. Do not pass that secret through browser redirects or query strings. Otherwise anyone with a code or refresh token can ask your proxy to use your client_secret for them.
  • Encrypt tokens at rest. WordPress core stores wp_options values as plaintext. Google Site Kit’s Data_Encryption is the de-facto reference implementation — AES-256-CTR with a key derived from LOGGED_IN_KEY in wp-config.php. Most major plugins (Jetpack, WooCommerce Stripe, Mailchimp for WooCommerce) don’t encrypt at all; doing so puts you ahead of the ecosystem default.
  • Understand what encryption buys you. It only mitigates stolen SQL backups, misconfigured phpMyAdmin, read-only DB leaks, and compromised noisy neighbours on shared MySQL. It does not mitigate a compromised WordPress admin, an RCE on the host, a malicious sibling plugin, or a wp-config.php leak — in all those cases the key is on the same box.
  • Narrow your scopes aggressively. Request only what the plugin genuinely needs. Every scope you request is a scope an attacker inherits.
  • Rotate refresh tokens through the proxy and persist the replacement. Stora rotates refresh tokens on every successful refresh. The plugin must send the current refresh token to the proxy, the proxy must refresh with the client_secret, and the plugin must atomically replace both the access token and refresh token it has stored.
  • Accept that you cannot revoke one installation without revoking the operator’s entire OAuth grant. Without a broker-issued token layer, there is no local kill switch granularity — you either revoke the Stora grant or you don’t.
Even with all of the above, the integration’s security posture is bounded by the weakest plugin installation running it. The authorization proxy protects your client_secret; it does not remove the plugin host from the Stora token trust boundary. The broker pattern does, which is why we recommend it for anything touching customer PII, payments, contracts, access control, or other sensitive data.

Next steps

You now have the OAuth side built. Before going live, also review:

Partner programme requirements

Timeline Events, idempotency, error handling, scope principles.

Rate limiting and backoff

Per-operator limits apply. Your broker or plugin must handle 429s, depending on the pattern.

Webhooks

React to Stora events instead of polling. Your broker or proxy receives them on a single URL.

Authentication reference

Full details on OAuth flows, token exchange, and token refresh.
Questions about any of the above, or an integration pattern that doesn’t fit these options? Contact us before you start building — it’s cheaper than rebuilding.