Project portfolio Browse selected work

Shopify Plus Upgrade Monthly Fee Reduction + Up to $4800 Development Fee Credit - Exclusive WesWoo Offer

Guide

Shopify App Proxy Integration: Signed Requests, Cache Boundaries, and Recovery

Published: Editorial review: 2026-08-30

Define the storefront proxy contract first

Treat the proxy as a verifiable request path

Shopify App Proxy is useful when an app needs to present a bounded feature inside the online store: a member summary, a preference panel, a service status, or another controlled storefront view. It is not a reason to move an entire administration console into a theme and it does not bypass Shopify authorization. Before writing business code, specify the proxy prefix, subpath, allowed methods, response type, store context, time budgets, cache posture, and shutdown behavior. Each item should be traceable between the app configuration, rendered storefront link, request log, and server response.

Start with a read-only path that cannot change an order or payment. Exercise a normal request, a missing signature, a store where the app is unavailable, a slow dependency, a repeated submit, a cache hit, and a failed theme script. A shopper should still see a useful explanation, ordinary navigation, product context, and a contact or return path. The engineering record should identify the store context, route version, latency, outcome, and failure category without exposing secrets.

The official App Proxy configuration documentation explains the relationship between the configurable prefix, subpath, and storefront route. Keep this implementation focused on the storefront proxy boundary; a general administration page, payment flow, and data synchronization job need different contracts.

Design the URL and response contract

Fix the route shape before adding features

A maintainable route makes its feature domain clear, such as /apps/profile/status or /tools/loyalty/summary. Once a path appears in theme links, bookmarks, campaigns, and monitoring, changing it creates a chain of old links, cache keys, and dashboards. Register the path, method, parameters, response type, and version policy before implementation. If a name must change, leave a short-lived explanation or controlled compatibility route rather than returning a silent blank screen.

Keep sensitive values out of the query string. Every allowed parameter needs a type, length, enumeration, and default rule; unknown values should be logged safely and ignored or rejected according to the contract. For JSON, define required fields, empty arrays, status values, and user-readable errors. For HTML, define the allowed fragment, cache headers, security policy, and theme dependencies. A server should not emit a large page fragment that only works with one theme’s private class names.

Design itemRecommended constraintFailure symptomAcceptance evidence
Prefix and subpathOne stable path per feature domainLinks and metrics are fragmentedApp settings, theme link, request log
HTTP methodSeparate reads from state changesA GET accidentally changes stateMethod matrix and replay test
ParametersType, length, enum, and defaults are explicitBad input consumes backend timeValidation log and response
Response typeChoose a primary HTML or JSON contractBrowser sees a blank successHeader, status, and body
Version policyOld paths have an explanation and end dateTheme update creates silent failureLink inventory and recovery test

Place the finished route in a simple Liquid implementation pattern. The theme owns presentation and submission; it should not reproduce the signing algorithm or place a long-lived credential in browser code.

Verify the proxy signature before business parsing

Preserve the original query representation

Signature verification must happen before business code trusts the query fields. Preserve the original query representation, remove only the documented signature field from the signing set, apply Shopify’s required sorting and concatenation rules, compute the server-side HMAC, and compare the result in constant time. Do not casually decode plus signs, percent escapes, arrays, or Unicode values and then rebuild the string. Two layers that normalize in a different order will calculate different values.

Keep signature fields and business fields distinct. Reject a missing digest, an invalid timestamp or an impossible shop value with one consistent external response. Do not reveal whether a store exists, which secret was used, or how the expected digest differed. A secret belongs in server configuration only; it must never be placed in theme settings, HTML, browser storage, or an error payload.

Verification stageInputRequired conditionRejection behavior
CaptureOriginal query and methodEncoding and repeated keys remain availableLog a safe request fingerprint
NormalizeAll fields except the signatureOne deterministic rule for every localeReturn a uniform 4xx
ComputeServer secret and normalized valueHMAC plus constant-time compareDo not disclose expected digest
Contextshop, path, and install stateStore format and route are allowedAvoid store enumeration
Business gateVerified values onlyType, permission, and status checks run againTrack auth and business errors separately

Use the official public app proxy authentication method to understand the framework context and failure behavior, then verify it with fixed request samples. A valid signature proves request context, not permission to perform every business action.

Bind store context and short-lived sessions

Make every read belong to the right tenant

The verified shop context should be the first condition in every database, cache, and downstream lookup. A browser-submitted shop value must never replace it. Normalize host casing, a trailing dot, Unicode hostnames, and empty values before business code runs; reject values that cannot be normalized safely. When the app is unavailable for a store or authorization has expired, return a comprehensible install or return path rather than a detailed account diagnostic.

Create a short session only when the feature needs one, and bind it to the store, user or session subject, expiry, and revocation state. Do not place a long-lived access token in a cookie, URL, or HTML. If an Admin read is needed, use a server-side token appropriate to the access model and request only the fields required by the page. Clear related cache entries when authorization or identity changes so a browser cannot receive an earlier user’s state.

The Shopify authentication and authorization guide is a useful reference for separating authentication context from permission checks. Store context answers which shop made the request; user identity and role answer who may perform the action. Logging those concepts separately makes an incident easier to diagnose without logging personal data.

Set cache keys and invalidation boundaries

Never let a fast response cross stores

Whether a proxy response can be shared depends on whether it contains store, user, or session data. A public explanatory fragment can have a short TTL. A response containing store settings, membership state, addresses, or choices should not be shared across users or stores by default. A cache key should include normalized shop, proxy path, allowed business parameters, locale, market context when relevant, and a data version. A path-only key is not sufficient.

Define hit, miss, stale, and explicit invalidation behavior for each response type. Installation state, uninstallation, permission changes, store settings, and theme changes can make prior data unusable. If invalidation cannot be proven, reduce the TTL or turn off shared caching. Write responses should carry non-cacheable behavior and use an application idempotency key. Cache records should expose only a safe key digest, age, version, and outcome in logs.

Response classSharing ruleCache keyInvalidation eventSafe fallback
Public explanationShort shared TTL is acceptablePath, locale, content versionContent or version changeStatic fragment
Store settingStore scope onlyShop, path, setting versionSetting save or app removalDefault setting
User stateUsually privateShop, session subject, pathSign-out or permission changeAsk for verification
Write resultNever sharedIdempotency key and request fingerprintCompletion or expiryReturn retry state
Error responseVery short or disabledRoute and status classService recoveryOrdinary error page

Test two stores and two browser sessions before measuring hit rate. An improved hit rate is not a reason to remove a store or session dimension from the key; an accidental cross-store response is more damaging than one additional origin read.

Budget slow dependencies and timeouts

Turn waiting into a clear state

Give the proxy a total deadline, connection deadline, and downstream read deadline. Each dependency needs a smaller budget so signature verification, cache lookup, database work, remote calls, and rendering fit inside the total. On timeout, return an explicit status and a readable message. An HTML response should retain store navigation; JSON should expose a stable error category and retry guidance. Never keep retrying after the browser has gone away, and never use unbounded retries that turn one click into a traffic spike.

For a read-only summary, a briefly stale value may be acceptable when its age is visible. For a membership entitlement, price, inventory decision, or other state that changes a shopper’s action, it is safer to say that the value is temporarily unavailable than to present an old value as current. Retry only a safe idempotent read, with backoff and a strict cap. For a write, check whether the prior operation completed before offering another attempt.

The storefront should offer one retry and an ordinary alternative, such as returning to a product page, contacting support, or continuing to browse. Error text must not include a stack trace, database name, token, signature digest, or dependency address. Group timeout evidence by route, store, dependency class, and region so a single store problem is not confused with a platform slowdown.

Make writes idempotent and replay-safe

Decide whether an action can be repeated

Proxy write actions might save a preference, register a reminder, submit a controlled request, or create a result record. Each action needs a business key and an idempotency key bound to the store, user or session subject, action type, and validity period. Inside a transaction, check for an existing completed result before creating a new record. Use a unique constraint or lock for concurrent requests. A response should let the browser query a known result after a timeout instead of blindly creating a second record.

A browser-generated random value is useful for correlation, not authorization. Authorization comes from the verified store context, server-side session, and field-level permission checks. For an action that cannot be repeated, return the original state on a second request. For a preference that can merge, apply the latest valid version and record the conflict. Disabling a button is not sufficient because refreshes, network retries, and multiple tabs can bypass it.

ActionIdempotency keyDuplicate resultUser action after failureEvidence
Save preferenceShop, subject, preference, versionReturn saved versionReload current versionRequest fingerprint and version
Register reminderShop, subject, productReturn existing registrationShow already registeredUnique constraint and state
Submit requestShop, subject, request keyReturn original stateQuery result stateTransaction record
Generate resultShop, subject, operation keyReturn same result addressOpen result addressResult version and time
Cancel actionOriginal key and cancel versionReturn final stateDo not resendState-machine record

Use refresh, double-click, two tabs, timeout, and concurrent requests in the replay suite. The Shopify access token guidance helps keep server token scope and lifetime separate from the lifetime of a single storefront action.

Make HTML, JSON, and errors recoverable

Preserve basic storefront usability

An HTML proxy fragment should depend on as little theme-specific styling as possible and should remain readable when a script does not load. A success response needs a title, status explanation, primary action, and return link. An error uses the same navigation and accessible landmarks. A JSON response needs a stable status, error category, user message, and correlation number; a frontend should never infer an error from a 200 response with an empty body.

Escape content for its context and reject unprocessed HTML, URLs, and user input in templates. If controlled rich text is necessary, sanitize it with an allowlist of tags, attributes, and protocols. Store settings must not become scripts or styles. Match response headers to the content, set a deliberate cache policy, and confirm the actual browser result rather than trusting a server unit test.

Test keyboard focus, visible error text, retry feedback, and long messages on a narrow viewport. The mobile conversion practice provides useful page context for this check. A failed proxy should not trap a shopper inside an empty drawer or a scroll-locked modal.

Minimize permissions, tokens, and sensitive data

Supply only what the storefront feature needs

List every field a proxy feature reads and writes, then map it to app permissions, store settings, and the user role. A store-level task should not send personal data to the browser. A server-side query should not require a token in a page. Use redacted identifiers in logs, and set a retention period for addresses, phone numbers, email addresses, and custom fields when troubleshooting genuinely needs them.

Separate credentials by purpose: a server token for controlled server-to-Shopify reads, a short session for storefront state, and a correlation number for support. Secrets, tokens, signature material, and complete cookies should never appear in HTML, query parameters, analytics events, or exception traces. During rotation, verify the new credential before revoking the old one. After removal or revocation, the proxy should recognize the invalid state and stop further access.

Review the Shopify secure token recommendations. Security is not one extra check; it is a set of boundaries that prevents data, permissions, caching, logs, and recovery behavior from widening exposure together.

Prove the request with safe observability

Build a useful timeline without secrets

Generate a correlation number for every request. Record a safe store digest, proxy path, method, status, total time, phase timings, cache result, dependency category, and failure class. Never record the full signature, token, personal data, or a query string that can be reconstructed into sensitive information. Return the correlation number in a user-facing error so support can find the timeline without asking the shopper for a secret.

The test matrix should cover an invalid signature, absent shop, unavailable app, missing permission, parameter boundaries, cache separation, timeouts, duplicate writes, downstream 4xx and 5xx responses, failed HTML scripts, and a narrow mobile view. Save the request sample, status, visible text, log events, and recovery result for each case. Monitor by route, store, region, and response class; alert on signature rejection, timeout, duplicate conflict, and cache-isolation failures.

ScenarioKey telemetryShopper resultPassing condition
Invalid signaturePath, status, correlation numberUniform rejection explanationNo sensitive detail leaks
App unavailableStore digest and status classInstall or return pathStore existence is not disclosed
Cache hitKey digest, age, versionCorrect contentTwo stores never share state
Downstream timeoutPhase times and dependency classRetry or ordinary linkRequest ends within budget
Duplicate writeKey digest and final stateOriginal result or completed stateNo duplicate record

Use the Shopify page-building trade-off guide to review whether the proxy link, theme script, and ordinary navigation remain understandable. A backend 200 count is not proof that shoppers received a usable page.

Failure cases and a practical recovery boundary

Stop exposure before investigating the root cause

Failure case one: middleware decodes the query string before the signature routine, and a plus sign is later reconstructed using a different rule. Some valid requests are rejected, so the storefront appears to have no app. Compare a fixed raw request, the normalized signing value, and the computed digest. The narrow repair is to restore one normalization path, not to weaken verification or change the business route. Repeat samples with alternate encodings, repeated keys, and empty values.

Failure case two: the cache key contains only /apps/profile/status and omits the store and session subject. After one browser warms the cache, another store sees an old status summary. Immediately disable shared caching, clear affected keys, and keep the correlation numbers. Add store, session, and data version dimensions, then test two stores, two browsers, and concurrent requests. If the historical response range cannot be demonstrated, keep shared caching off and notify the operations owner.

The recovery boundary includes proxy configuration, the theme entry point, the cache switch, the response template, and the related middleware. It does not include deleting products, orders, customers, or unrelated theme pages. Prepare a read-only status page or ordinary link as a safe entrance. After recovery, verify navigation, product context, and contact access, then repair one signature, cache, or response rule at a time.

Release, pause, and re-enable deliberately

Make the integration an operable capability

Before release, check the prefix, subpath, methods, parameters, signature verification, store context, session expiry, cache key, timeout budget, idempotency key, error response, redacted logs, and ordinary entrance. Exercise one store without the app, one installed store, one slow network, and one narrow device. Confirm that a theme update preserves the link and that removal or permission loss stops access.

After enabling, watch fixed stores and the read-only feature before allowing writes. Group signature rejection, timeout, cache isolation, duplicate conflict, script failure, and support feedback by route, store, region, and response class. During a pause, close writes or shared caching first while keeping the explanation and ordinary navigation available. If the root cause is unclear, switch to a static status page and re-enable one function only after evidence is complete.

The store speed field guide and the media experience guide help position a proxy page within the full storefront experience. Keep the proxy as a bounded storefront capability with clear ownership for theme, permissions, data, and recovery.

For a release record, capture the configured prefix and subpath, the rendered link, HTTP method, request body class, raw query sample, normalized signing value, timestamp decision, store-context digest, session state, cache key digest, downstream budgets, response content type, status, visible error text, and correlation number. A browser screenshot alone cannot reveal whether the wrong store entered a cache or whether a middleware layer changed the signed representation. Keep one trace for an anonymous visitor and another for an authorized session, with secrets and personal data removed.

Exercise the route through a normal link, a refresh, a browser back action, two tabs, a slow dependency, an unavailable dependency, a repeated write, an app removal state, and a changed prefix or subpath. Check that every branch ends within its budget, returns a predictable content type, preserves ordinary navigation, and leaves enough evidence for support to reproduce the symptom. When the feature is paused, verify the same links again so a disabled route produces an explanation instead of a blank success. Re-enable one read path first, then one write path, and compare the stored timeline with the earlier baseline before widening traffic.

Frequently asked questions

Can an App Proxy replace backend authorization?

No. The signed request provides Shopify request context, but every business action still needs app permission, session, identity, and field-level checks. Use a server-side token for required Shopify reads and request the smallest useful scope.

Why can signature validation pass locally and fail online?

A proxy, server framework, or middleware may change query encoding, repeated keys, or sorting in the online path. Preserve a raw request sample and compare the normalized signing value and each calculation step instead of comparing only the final URL.

Can a proxy response be cached for a long time?

Only a response with no store, user, or session data is a good shared-cache subject, and it still needs a version and invalidation rule. Personalized state, write results, and permission-related data should be isolated by store or session; when isolation cannot be proven, do not share it.

What should a shopper do after a backend timeout?

Show a clear temporary-unavailable or retry message while keeping an ordinary navigation path. Retry only a safe idempotent read with a cap. For a write, let the shopper query the original result so refresh does not create a duplicate.

What should be paused first after cross-store content appears?

Disable shared caching or writes and switch to a read-only status page or ordinary link while preserving request evidence. Do not delete commerce data. Verify the core entrance, then repair the signature, cache key, or response template that caused the exposure.