# Content Security Policy and allowed origins

_Category: Widget and embedding_

The widget, FAQ sections and the embedded help center load scripts, frames and data from HelpCenter.io. If your site sends a Content Security Policy, it must allow them. This page lists the exact hosts and directives, the other browser settings that matter, and how **Embedding origins** decides where your help center can appear.

## HelpCenter.io hosts

| Host | What your page loads from it |
| --- | --- |
| `https://helpcenter.io` | The widget's loader, `/js/init.js`, and the scripts it loads when it needs them (under `/js/`): the proactive-message engine and the FAQ sections script. The embed script, `/js/embed.js`. |
| `https://embed.helpcenter.io` | The widget's frame. Requests your page makes for the widget's configuration, its proactive messages and your FAQ sections. |
| `https://metrics.helpcenter.io` | Usage statistics your page sends for the widget and FAQ sections. |
| Your help center's address | The embedded help center: `https://acme.helpcenter.io`, or your custom domain. |

These hosts are the same for every help center. A custom domain changes only your help center's own address.

## The widget

Add these sources to your policy:

```
Content-Security-Policy: script-src 'self' 'nonce-RANDOM' https://helpcenter.io; frame-src https://embed.helpcenter.io; connect-src 'self' https://embed.helpcenter.io https://metrics.helpcenter.io
```

| Directive | Source | Without it |
| --- | --- | --- |
| `script-src` | `https://helpcenter.io` | Nothing loads. |
| `script-src` | A nonce or hash for the snippet, or `'unsafe-inline'` | The dashboard's snippet is an inline script, so it's blocked and nothing loads. You can also move it out of the page, below. |
| `frame-src` | `https://embed.helpcenter.io` | The widget never appears. |
| `connect-src` | `https://embed.helpcenter.io` | The widget appears, but proactive messages never show, and usage statistics aren't recorded. |
| `connect-src` | `https://metrics.helpcenter.io` | The widget works, but its usage statistics aren't recorded. |

With a nonce, your server adds a new random value to each response's policy and to the snippet's `<script>` tag, for example `<script nonce="RANDOM" type="text/javascript">`. To avoid inline scripts altogether, set `window.hcOptions` in one of your own script files and load `init.js` with a script tag; see [Widget installation and options](https://developers.helpcenter.io/content/widget-options):

```
<script src="/js/help-widget-options.js"></script>
<script src="https://helpcenter.io/js/init.js" async></script>
```

The widget's frame is its own document: its fonts, styles and images don't count against your page's policy.

## FAQ sections

FAQ sections render on your page itself, so their styles and images do count against your policy. On top of the widget's sources, allow inline styles, and the hosts the images in your FAQ answers load from:

```
Content-Security-Policy: script-src 'self' 'nonce-RANDOM' https://helpcenter.io; frame-src https://embed.helpcenter.io; connect-src 'self' https://embed.helpcenter.io https://metrics.helpcenter.io; style-src 'self' 'unsafe-inline'; img-src 'self' https://images.example.com
```

Without `'unsafe-inline'` in `style-src`, the questions and answers still load, but without their styles. To see where an answer's images load from, open the article on your help center and check the image's address.

## The embedded help center

For the full help center in a frame, allow the embed script, the inline script that sets `subdomain`, and your help center's address:

```
Content-Security-Policy: script-src 'self' 'nonce-RANDOM' https://helpcenter.io; frame-src https://acme.helpcenter.io
```

Give the inline script the nonce, `<script nonce="RANDOM">`, or set `subdomain` in one of your own script files. With a custom domain, put it in `frame-src` instead, such as `https://help.acme.com`. On a private help center, the frame also visits your Login URL to sign readers in, so add its origin to `frame-src` as well, such as `https://app.example.com`. The embed itself is covered in [Embed your help center in your app](https://developers.helpcenter.io/content/embed-your-help-center).

## Other browser settings

- **Referrer-Policy.** Keep at least your page's origin in the referrer sent to other sites, as the default policy, `strict-origin-when-cross-origin`, does. With `no-referrer` or `same-origin`, the widget doesn't render in Firefox, and its console says "Parent domain not found. Cannot render the widget." An embedded help center with JS-only access on shows "Help center not available."
- **Cookies.** The widget sets no cookies on your site, and its sign-in with a JWT uses none, so blocking third-party cookies doesn't affect it. An embedded private help center keeps readers signed in with a cookie, which works only when your page and your help center share a site; see [Embed your help center in your app](https://developers.helpcenter.io/content/embed-your-help-center).
- **Storage.** On your page, the widget keeps two identifiers for usage statistics in `sessionStorage`, `hc_session_id` and `hc_journey_id`. With proactive messages active, it also keeps when each message was shown in `localStorage`, under `hcw:nudge:` followed by your widget ID.

## Embedding origins

Embedding origins is the list of hosts where your help center may appear. Owners and Admins edit it in **Settings** → **General**, or in **Embeddables** → **Widget** → **Configuration**; see [Choose which websites can show the widget](https://self.helpcenter.io/content/embedding-origins).

![Screen recording. Under Embedding origins, add the site that shows your widget. A leading dot covers every subdomain. Click Save changes.](https://helpcenter-io.s3.amazonaws.com/uploads/self/SwdRQFTVx9bpPQ1ny4oVKkgkxHOxDnDeswlwj8VT.gif)
The same list covers the widget and the embedded help center.

### How entries match

- **Hosts only.** A full address such as `https://www.example.com:8443/help` is saved as `www.example.com`.
- **Exact hosts.** `example.com` and `www.example.com` are different entries.
- **A leading dot for subdomains.** `.example.com` covers `example.com` and every subdomain of it. The widget doesn't recognize other wildcard forms, such as `*.example.com`.
- **Ports, for the widget only.** The widget ignores the port, so `localhost` covers `http://localhost:3000`. The embedded help center needs an `https://` page on the standard port.
- **An empty list blocks everything.** With no entries, the widget and the embed are refused on every website.

### What the list controls

| Surface | What happens on a host that isn't in the list |
| --- | --- |
| The widget | It doesn't render or take commands, and the frame's console says "Parent domain is not in the allowed list." followed by the host. It checks the host of the page it's placed on. |
| Proactive messages | They don't load. |
| The embedded help center | The browser refuses to show it. Every page around the frame must be in the list, not only the page that contains it. |
| JS-only access | Following a link from that host to your help center shows "Embedding not allowed." |

FAQ sections have their own list of websites, **Display on URLs**, and are for public content. See [Embed FAQ sections on your website](https://self.helpcenter.io/content/faq-sections).

The Embedding origins list decides where the help center can be shown, not who can read it. To limit who can read your articles, make your help center Private and sign readers in; see [Sign readers into the widget with a JWT](https://developers.helpcenter.io/content/widget-jwt).

## Your help center's frame-ancestors

Your help center's public pages tell browsers which pages may frame them. With Embedding origins set to `localhost`, `127.0.0.1` and `app.example.com`, every page answers with:

```
Content-Security-Policy: frame-ancestors 'self' localhost 127.0.0.1 app.example.com
```

An entry with a leading dot appears twice, as `example.com *.example.com`. With an empty list, the header is `frame-ancestors 'none'`. Sources without a scheme or port match only `https://` pages on the standard port, which is why `http://localhost:3000` can't show the embedded help center. After you save the list, the new header can take a couple of minutes to reach every visitor.

## IP allow-list

When your help center allows only listed IP addresses (**IP whitelisting** in **Settings**; see [Restrict access by IP address](https://self.helpcenter.io/content/ip-allowlist)), visitors from other addresses can't read it anywhere:

- The widget shows **Not available on this network** and "This help center can only be opened from approved networks. If you usually have access, connect to your organization's network or VPN and try again."
- The widget's requests answer `403 Forbidden`, and no token changes that:

```
{
  "status": "error",
  "message": "This help center is not available from your network.",
  "code": "SITE_IP_RESTRICTED"
}
```

On the help center itself, those visitors are sent to the HelpCenter.io sign-in page. Addresses must match exactly: the list has no ranges. It applies to everyone, including your team and readers who sign in with a JWT.

## Troubleshooting

| What you see | Cause | Fix |
| --- | --- | --- |
| No launcher, and "Parent domain is not in the allowed list." in the console | Your page's host isn't in Embedding origins. | Add the host the message names, and reload after a couple of minutes. |
| No launcher, and a Content Security Policy error for `script-src` in the console | Your policy blocks `init.js` or the inline snippet. | Allow `https://helpcenter.io`, and a nonce or hash for the snippet. |
| No launcher, and a Content Security Policy error for `frame-src` | Your policy blocks the widget's frame. | Allow `https://embed.helpcenter.io` in `frame-src`. |
| The widget works, but proactive messages never show, and the console has a `connect-src` error | Your policy blocks requests to `https://embed.helpcenter.io`. | Allow it in `connect-src`. |
| The widget works in Chrome and Safari but not in Firefox | Your page's `Referrer-Policy` is `no-referrer` or `same-origin`. | Use `strict-origin-when-cross-origin` or another policy that sends the origin. |
| A FAQ section shows without its styles | Your policy blocks inline styles. | Add `'unsafe-inline'` to `style-src`. |
| The embedded help center stays blank, with a `frame-ancestors` error in the console | A page around the frame isn't in Embedding origins, or the page isn't `https://` on the standard port. | See [Embed your help center in your app](https://developers.helpcenter.io/content/embed-your-help-center). |
| The widget says **Not available on this network** | Your IP allow-list doesn't include the visitor's address. | Add the address, or clear the list. |
| The widget's frame loads, but the widget never appears, and its request answers `410 Gone` with `WIDGET_NOT_FOUND` | The `app_id` in your snippet is wrong. | Copy the snippet again from **Embeddables**. |

More causes, from the account owner's side, are in [The widget doesn't show up](https://self.helpcenter.io/content/widget-not-showing).

## Related

- [Widget installation and options](https://developers.helpcenter.io/content/widget-options)
- [Embed your help center in your app](https://developers.helpcenter.io/content/embed-your-help-center)
- [Sign readers into the widget with a JWT](https://developers.helpcenter.io/content/widget-jwt)
