Common questions about security, privacy, and how the proxy works.
No. The proxy URLs (/apps/proxy/...) only exist on your Shopify storefront. When a request comes in, Shopify’s App Proxy gateway signs it with a cryptographic HMAC signature using your app’s secret key. The Cloudflare Worker verifies this signature before processing any request.
If someone tries to call the Worker URL directly (bypassing Shopify), the request has no valid signature and is immediately rejected with a 401 Unauthorized. There is no way to forge this signature without your app’s secret.
A visitor browsing your store (or using the browser console) can call your configured proxy routes - that’s by design, since your theme code needs to call them too. However, they can only hit the exact routes you configured with the exact target URLs and methods you defined. They cannot change the destination, inject different headers, or access other merchants’ data.
All proxy routes are also rate-limited to 120 requests per minute per route, and on routes that require login each customer gets at most 30 per minute. Monthly usage is capped by your plan limit, so even automated abuse is bounded.
No. API keys and secret headers are stored in Cloudflare KV and injected server-side by the Worker. They never appear in the HTML source, JavaScript bundles, network responses, or browser DevTools. The storefront visitor only sees the final API response data - never the credentials used to fetch it.
This is the entire purpose of the app: keeping your API keys completely hidden from client-side code.
Open Theme Scan in the app and click Run scan. It checks every file in your published theme for known secret patterns (Stripe, AWS, Klaviyo, OpenAI and many more) and skips keys that are public by design, such as Stripe publishable keys. We don’t store the matches.
For each finding, move that API call to a SecureBite route, then create a new key with the provider and delete the old one, since anyone may already have copied it. On Pro and Scale, our team can do the cleanup for you.
Your API keys stay hidden regardless of the HTTP method. However, for routes that write or modify data (POST, PUT, PATCH), be aware that any visitor on your store could potentially call that endpoint from the browser console. To mitigate this:
{{_customer_id}}) or a secret param ({{logged_in_customer_id}}).For highly sensitive write operations (payments, account creation), a dedicated backend with full user authentication is recommended over any client-side proxy.
The nonce system prevents replay attacks and console-scripted spam on POST routes. When enabled, the frontend must get a single-use token before submitting data:
GET /apps/proxy/{route}?_action=nonce to receive a nonce, timestamp, and cryptographic signature._nonce, _nonce_ts, and _nonce_sig in the POST body alongside the form data.This means each POST needs its own unique GET request first. Console-based spam scripts are slowed to one request at a time, and replaying a captured request always fails because the nonce is single-use.
A body template lets you define the exact JSON structure that gets sent to the upstream API. Instead of the frontend controlling the full payload, it only sends simple field values (like name and email). The Worker merges these into your template server-side.
Benefits:
{{_customer_id}} and {{_timestamp}} are injected server-side - the frontend can’t fake them.By default, query parameters the storefront sends (for example ?filter=… or ?page[size]=100) are passed on to your API. Anyone can edit those in the browser.
Turn on Forward only mapped params on the route. Every storefront parameter is then dropped, except those you use in a {{placeholder}} or a parameter mapping. Your secret query params are still added. Callers can no longer slip in their own filters, extra fields or paging.
If your storefront currently sends a value the API needs, such as a filter, set up its server-side replacement (a secret param) in the same save as you turn this on.
Yes. Write {{name}} in the path of the Target API URL, for example https://a.klaviyo.com/api/profiles/{{p}}/. It is filled from the storefront query parameter of the same name: /apps/proxy/your-route?p=01HZX4….
^[0-9A-Za-z]{26}$. If the value is missing or doesn’t match, the visitor gets an error and your API is never called._ and - are accepted./, ? or ../ can’t change which address is called. Placeholders are only allowed in the path, never in the domain.Yes, on the Pro and Scale plans (each has a 14-day free trial). Set Cache responses on a GET route and repeat requests get a stored copy instead of calling the API again. It works with any API and for every visitor, logged in or not. Requests that send different values to the API (another product, search term or customer) get separate copies, so a customer’s own data is only ever returned to them. Cached responses count toward your monthly requests.
SecureBite only sees changes that go through its own routes, so match the fix to where a change comes from:
_fresh=1 to the next request, for example /apps/proxy/my-route?_fresh=1. It skips the stored copy and stores the new data.Set Return only on the route to a path in the JSON response, for example data.attributes.properties. The visitor then receives only that part: { "properties": { … } }. Everything else the API returns, such as emails, IDs or internal fields, never reaches the browser.
If the API returns an error, the status code is passed on with a short message such as { "error": "not_found" }, instead of the API’s full error details.
Let SecureBite decide who the customer is, instead of trusting what the browser sends:
filter = equals(external_id,"{{logged_in_customer_id}}"). {{logged_in_customer_id}} comes from Shopify’s signed request, so it can’t be faked.Whatever filter the browser sends is then ignored, and the route can only ever return that customer’s own record. To match by email instead, use filter = equals(email,"{{logged_in_customer_email}}"). SecureBite looks the email up from Shopify on the server, so it can’t be faked either.
Klaviyo’s Get Events filters by profile ID, not email. On a route to a.klaviyo.com with Require logged-in customer on, use filter = equals(profile_id,"{{klaviyo_profile_id}}"). SecureBite finds the customer’s Klaviyo profile from their email with the route’s own Klaviyo headers, then makes the call. If the customer has no Klaviyo profile, the route returns {"data": []}. A request that needs this lookup counts as 2 requests.
The route editor’s help text explains each option, and Shopify Sidekick can review a route’s security for you.
Using SecureBite means you accept our Terms of Service, which include a data processing addendum for your customers’ data. Our Privacy Policy explains what we store and for how long, and our Security Incident Response Policy explains how we handle and report incidents.
Yes. Open Sidekick in your Shopify admin and paste your API call: a curl command, code or an example from the API’s docs. Sidekick turns it into a route and gives you a link that opens the route form already filled in. Nothing is saved until you check it and click Save route.
Sidekick never fills in your secret keys. You type them into the form yourself. If your example contains a real key, replace it with a placeholder such as YOUR_API_KEY before pasting.
Sidekick can also review a route’s security, give you the code to call a route from your theme, tell you how many requests you have used, and explain your Theme Scan findings.
Paid plans have unlimited routes and a 14-day free trial, and you are billed through Shopify.
Every request through a route counts, including cached responses. The app warns you at 80% of your monthly limit. At 100%, proxy requests are blocked until the next month starts, and we email you. On Free, delete a route or upgrade to add a third one.
On the Free plan, use the Contact support button inside the app. Paid plans get live chat in the app. You can always email support@webite.rocks.