---
title: "Call a Shopify Flow webhook from a browser - CORS and app proxy"
description: "Enable CORS, publish the webhook on the shop's own domain with the app proxy URL, and know what a browser-callable webhook exposes."
canonical: "https://docs.workflow-webhooks.app/cors-and-app-proxy"
---

# CORS, the app proxy URL and browser calls

A webhook is normally called server to server. If you want to call one from a **browser** - your storefront, a landing page, a customer portal - two settings matter: **Enable CORS** and **Use app proxy URL**. Both live under the webhook's **Advanced settings**.

## Enable CORS

Browsers refuse a cross-origin request unless the receiving server says it is allowed. Switch on **Enable CORS** and the webhook answers the browser's preflight and accepts the call.

The preflight also allows whichever header carries your token, including a custom header name you set yourself, so a browser does not trip over it.

> [!WARNING]
> **Anything in a browser is public**
> A static token in front-end code is readable by every visitor: view-source, the network tab, or a copy of your bundle. Treat a browser-callable webhook as one that anyone can call.
>
> If that is not acceptable, do not call the webhook from the browser at all - post to your own backend and let that call the webhook with the secret. Use the browser path for things that are harmless to trigger, and keep the plan limit in mind, because every call counts.

## Use app proxy URL

Every webhook has two addresses, both shown with a copy button under **URL settings**:

| URL | Looks like | Notes |
| --- | --- | --- |
| Direct app URL | our app domain, ending in the webhook's code | Always works, including on development and password-protected stores |
| App proxy URL | a path on the **shop's own domain** | Same origin as your storefront, so a browser treats it as a first-party request |

Switch on **Use app proxy URL** to publish the proxy address. It integrates more cleanly with the storefront, and because it is same-origin you often do not need CORS at all.

Two things to know before you turn it on:

- The shop must be **live and not password protected**. Shopify does not route app proxy requests for a locked storefront, so on a development store the proxy URL will not answer. Keep it off there.
- The [IP allowlists](https://docs.workflow-webhooks.app/ip-allowlist.md) does not apply to the proxy URL: those requests reach us from Shopify's servers, so their address tells you nothing about the original caller.

Authentication works the same on both addresses. So does [Duplicate delivery protection](https://docs.workflow-webhooks.app/duplicate-delivery.md), history and the plan limit.

## Allow a custom request body

Also in Advanced settings: **Allow custom request body** switches off strict body validation, so the webhook accepts any JSON shape rather than only the fields you mapped. Use it when the sender's payload is dynamic or you do not control it. Mapped fields still reach Flow the same way - see [Payload mapping and Flow variables](https://docs.workflow-webhooks.app/payload-mapping.md).

## Related

- [Authentication](https://docs.workflow-webhooks.app/authentication.md) - what the browser has to send.
- [Payload mapping and Flow variables](https://docs.workflow-webhooks.app/payload-mapping.md) - which fields reach Shopify Flow.
- [Plans and usage](https://docs.workflow-webhooks.app/plans-and-usage.md) - every browser call counts toward the limit.
