Skip to main content

Allowed origins — the security allowlist

Your pk_live_ key is public. It sits in HTML on your website where anyone with view-source can read it. That’s fine — the moat isn’t the key value, it’s the allowed origins attached to it. Even if someone lifts your key and pastes it on their own site, our API will reject their request because the browser’s Origin header doesn’t match your allowlist.

This page explains what counts as an origin, how to add one, and the small handful of traps that make an otherwise-correct embed return origin_denied.


The rule in one sentence

Your widget request will only succeed if the exact string in the browser’s Origin header (scheme + host + port) appears in your key’s Allowed Origins list.

Everything else on this page is a specific case of that rule.


What counts as an origin

An origin is three parts glued together with no spaces:

<scheme>://<host>:<port>
  • Scheme: http or https
  • Host: the domain name — yourchurch.org, podpodcast.com, localhost
  • Port: the port number. Omitted for the browser defaults — 80 for http and 443 for https. Explicit for anything else (localhost:3000, myapp.dev:8080).

Two origins that differ in any of those three parts are different origins. The most common surprises:

ABDifferent?
https://yourchurch.orghttps://www.yourchurch.orgYes — different host
https://yourchurch.orghttp://yourchurch.orgYes — different scheme
https://yourchurch.orghttps://yourchurch.org:8443Yes — different port
https://yourchurch.orghttps://yourchurch.org/sermonsNo — path doesn’t count
https://yourchurch.orghttps://yourchurch.org/No — trailing slash doesn’t count

The rule sounds pedantic until it saves you from a widget that worked in staging but silently 403s on your live domain.


What does not count as part of an origin

None of these matter — you can ignore them entirely:

  • Path. https://yourchurch.org/sermons and https://yourchurch.org/podcast are the same origin.
  • Query string. ?foo=bar never appears in Origin.
  • Fragment. #section-2 never appears in Origin.
  • Trailing slash. The origin doesn’t have one.
  • User info in URL. https://user:[email protected] — the user:pass@ part doesn’t appear in Origin.

Add the base — https://yourchurch.org — and every page on the site is covered.


Adding an origin

  1. Go to Console → Settings → Allowed Origins.
  2. Click Add origin.
  3. Paste the full origin — scheme included, no trailing slash, no path. Examples:
    • https://www.yourchurch.org
    • https://podpodcast.com
    • http://localhost:3000
  4. Save.

The change is live immediately. The widget on that origin will succeed on its next request — usually the next page reload — with no code change required.


Removing an origin

Same page. Click the × next to the entry. Save.

Removal is also immediate. Any widget request from that origin starts returning origin_denied at once. Use this if you sunset a domain, cut a staging site, or think a preview URL is being abused.

Removing an origin does not invalidate your key. Requests from other origins in the list continue to work.


Common allowlist recipes

Production only

One entry, covers the whole site.

https://www.yourchurch.org

If you also serve the apex (https://yourchurch.org) via a redirect or dual-hosting, add both — even if one immediately redirects to the other, the browser makes the request from the origin it’s loaded from, before the redirect.

Production + apex

Every request from either name works.

https://www.yourchurch.org
https://yourchurch.org

Recommended for any site where you’re not 100% sure which host customers land on.

Production + local development

https://www.yourchurch.org
http://localhost:3000

Add whatever port your dev server uses. Common defaults: Next.js 3000, Vite 5173, SvelteKit 5173, Astro 4321. http, not https — you’re on localhost.

Production + Squarespace staging URL

Squarespace serves your site from yoursite.squarespace.com before your custom domain is fully wired and after, if you keep the subdomain. Both are legitimate.

https://yourchurch.org
https://www.yourchurch.org
https://yourchurch.squarespace.com

Production + one staging environment

Common for WordPress + WP Engine or similar.

https://yourchurch.org
https://www.yourchurch.org
https://yourchurch.wpengine.com

Production + Vercel / Netlify preview URLs

See the next section — this one’s messy enough to have its own.


Ephemeral URLs (Vercel, Netlify, Render, Fly)

Vercel gives every branch and every commit its own preview URL — something like yourapp-git-branch-team.vercel.app. Netlify does the same with yourapp--branch.netlify.app. Each preview is a separate origin.

Our allowlist is currently exact match. Wildcards are on the roadmap but not shipped. Meanwhile:

  • Recommended for teams: email [email protected] with your Vercel / Netlify team slug. We can bulk-add your preview URL pattern to your key on our side. One-time setup.
  • Recommended for occasional testing: add the specific preview URL manually before you click the link. Remove it after. Fine for a first deploy; not sustainable for CI-generated previews.
  • Recommended for solo devs: test locally on http://localhost and only rely on the production URL after deploy. Preview URLs become optional.

Production URLs (your custom domain) are unaffected either way — those go in the allowlist as normal.


Browser extensions and the null origin

Web pages loaded from a browser extension send Origin: null, which by default any origin allowlist would reject. If your widget needs to work inside an extension — for example the PodSaid YouTube Channel Extension — set Allow browser extensions to on for that specific key.

You’ll see the toggle on the key detail page in Console → Settings → Embed Code. It’s on by default for keys minted for extension use and off otherwise. Turning it on for a production website key doesn’t loosen any other check; it only adds null as a legal Origin value.


Debugging origin_denied

If the widget is returning origin_denied, the fastest way to fix it is to look at exactly what the browser sent us.

  1. Open your page. Open DevTools → Network tab.
  2. Find a request to api.podsaid.com — usually the /verify endpoint fires first.
  3. Click it. Look at Request Headers → Origin. That string — copy it verbatim.
  4. In Console → Settings → Allowed Origins, paste it. Save.
  5. Reload the page. The widget should authenticate on its next request.

If the Origin header your browser is sending doesn’t look like your website URL, something between your browser and our API is rewriting it — usually a corporate proxy, a browser extension, or a translation service. That’s a rarer failure than a plain missing allowlist entry; if you hit it, email support with the header value and we’ll help sort it.


When your allowlist is right and it still doesn’t work

The other reason codes catch what’s left:

  • Widget doesn’t send a key at all → key_missing — see key_missing.
  • Key is real but we don’t recognize the value → key_unknown — see key_unknown.
  • Key was intentionally shut off → key_revoked — see key_revoked.
  • Key is real but for a different feed than the feed-id attribute → cross_feed — see cross_feed.

The full list is at Widget errors.


Still stuck?