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:
httporhttps - Host: the domain name —
yourchurch.org,podpodcast.com,localhost - Port: the port number. Omitted for the browser defaults —
80forhttpand443forhttps. 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:
| A | B | Different? |
|---|---|---|
https://yourchurch.org | https://www.yourchurch.org | Yes — different host |
https://yourchurch.org | http://yourchurch.org | Yes — different scheme |
https://yourchurch.org | https://yourchurch.org:8443 | Yes — different port |
https://yourchurch.org | https://yourchurch.org/sermons | No — path doesn’t count |
https://yourchurch.org | https://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/sermonsandhttps://yourchurch.org/podcastare the same origin. - Query string.
?foo=barnever appears in Origin. - Fragment.
#section-2never appears in Origin. - Trailing slash. The origin doesn’t have one.
- User info in URL.
https://user:[email protected]— theuser:pass@part doesn’t appear in Origin.
Add the base — https://yourchurch.org — and every page on the
site is covered.
Adding an origin
- Go to Console → Settings → Allowed Origins.
- Click Add origin.
- Paste the full origin — scheme included, no trailing slash, no
path. Examples:
https://www.yourchurch.orghttps://podpodcast.comhttp://localhost:3000
- 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://localhostand 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.
- Open your page. Open DevTools → Network tab.
- Find a request to
api.podsaid.com— usually the/verifyendpoint fires first. - Click it. Look at Request Headers → Origin. That string — copy it verbatim.
- In Console → Settings → Allowed Origins, paste it. Save.
- 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-idattribute →cross_feed— see cross_feed.
The full list is at Widget errors.
Still stuck?
- Errors index: Widget errors — what each one means
- Book a 15-minute call with Mark: /docs/support
- Email:
[email protected]— include the exactOriginheader value from DevTools and your current allowlist