PodSaid Widget — FAQ for Web / IT Folks
This is the technical FAQ for the engineer, sysadmin, or agency lead responsible for the site that will embed <ask-pod-widget>. It covers the payload, CSP, CDN, versioning, security model, and known limits — no marketing fluff, honest about the gaps.
1. What exactly gets injected into our page?
One custom-element tag and one script tag. That’s it.
<ask-pod-widget
feed-id="yourfeed"
api-key="pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
</ask-pod-widget>
<script defer src="https://widget.podsaid.com/v1/widgets.iife.js"></script> The script defines a Web Component (ask-pod-widget). All markup, styles, and event listeners live inside a Shadow DOM root — nothing bleeds into your page’s CSS cascade or global JS scope. No iframes. No hidden DOM injection outside the tag you placed. No cookies set by the widget.
Attributes:
feed-id(required) — the tenant slug.api-key(required) — public, origin-locked (see §7). Formatpk_live_<48-hex>.api-url(optional) — override the defaulthttps://api.podsaid.com. Useful for staging.episode-id(optional) — scope the chat/search context to a single episode.
2. How big is the bundle and how does it load?
- IIFE bundle: 140 KB uncompressed, 47 KB gzipped.
- CSS: 11 KB uncompressed / 2.5 KB gzipped, inlined into the bundle — no second request, no separate stylesheet.
- Load mode:
defer. HTML parsing and page render are never blocked. - Fonts / icons: inlined. No Google Fonts, no Font Awesome, no third-party CDN pulls.
The custom element upgrades once the script runs. Before that, <ask-pod-widget> is an inert element in the DOM — no layout shift beyond the box you sized for it. If you want to reserve space, set a min-height on the tag in your own CSS.
3. What are the CSP requirements?
Minimal. The bundle does not use eval, new Function, or inline <script> blocks, so no unsafe-eval and no unsafe-inline for scripts is needed.
Content-Security-Policy:
script-src 'self' https://widget.podsaid.com;
connect-src 'self' https://api.podsaid.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:; Notes:
style-src 'unsafe-inline'is needed only if your page-level CSP is strict about styles — the widget’s own styles live inside the Shadow DOM but are attached via a constructed stylesheet, which some strict CSPs still flag. If you already allow'unsafe-inline'for styles you’re fine. If not, test with your policy and let us know.img-srcis for podcast/episode artwork rendered inside the chat surface — sourced from the customer’s own feed images (varies per tenant), sohttps:is the pragmatic allowance.- No
frame-srcneeded. The widget does not use iframes.
4. What CDN hosts it? What’s the cache policy?
- CDN: Cloudflare R2 fronted by a Cloudflare Worker at
widget.podsaid.com. Global edge, no third-party CDN dependencies. - Float URL (
/v1/...):Cache-Control: public, max-age=300(5 minutes at the edge). Every tag push in the source repo triggers CI → R2 upload → cache purge, so the 5-minute TTL is a ceiling, not a floor. - Lock URL (
/v1.2.3/...):Cache-Control: public, max-age=31536000, immutable. One year, immutable. Path is content-addressed by version; tag reuse is impossible. - API host:
api.podsaid.com— Caddy + Docker on a GCP VM. TLS terminates at Caddy.
5. What’s your release cadence and versioning policy?
Two channels, same bundle, different guarantees. We call this Float and Lock.
Float — https://widget.podsaid.com/v1/widgets.iife.js
- 5-minute edge cache TTL.
- Always points at the latest published
v1.x.x. - Every source-repo tag push (
widget-v<version>) triggers CI → R2 upload → cache purge. - Good default for teams that want continuous bugfixes without shipping a new deploy.
Lock — https://widget.podsaid.com/v1.2.3/widgets.iife.js
- Immutable.
Cache-Control: max-age=31536000, immutable. - Tag reuse is impossible; if an upload for a given version fails, the fix is a patch bump.
- Recommended for change-controlled environments (banks, regulated non-profits, agency deliverables where the client controls change windows).
Semver policy
- Patch (
v1.2.3→v1.2.4): bugfix or refactor. Safe on Float. - Minor (
v1.2.x→v1.3.0): additive features. Safe on Float — new attributes/props are opt-in; existing behavior does not change. - Major (
v1.x→v2.0): breaking change. The Float channel bumps to a new path —/v2/widgets.iife.js./v1/...never breaks under you. Teams on Float pin to a major channel implicitly; upgrading to v2 is an explicit URL change, not a surprise.
Copy-paste snippets
Float (recommended default):
<script defer src="https://widget.podsaid.com/v1/widgets.iife.js"></script> Lock (recommended for change control):
<script defer src="https://widget.podsaid.com/v1.2.3/widgets.iife.js"></script> Honest scope caveats
- SRI hashes are not published today. Because Lock URLs are immutable and content-addressed, teams that want SRI-equivalent guarantees should Lock to a pinned version. It’s a legitimate gap — see §12.
- Customer-scoped “smart Lock” URLs (server-side resolves to a per-customer pinned version, with kill-switch and canary) — roadmap, not shipped. Not a feature you can rely on today.
6. How do you test releases? What’s your rollback story?
Pre-publish: unit tests in the source repo (Svelte 5 → Web Component build), plus a headless-browser smoke that mounts the bundle against a live staging API and exercises the search + chat surfaces.
Post-publish: CI verifies the R2 upload matches the built artifact byte-for-byte before the cache purge fires. If the purge fails, the old bundle stays live until we push a fix — Float doesn’t half-deploy.
Rollback on Float: re-tag the previous known-good version as the newest tag. CI treats it as a normal release, uploads it, purges the edge. Time to rollback: minutes.
Rollback on Lock: you edit one string in one HTML file (the version number in the script src). Time to rollback: whatever your own deploy pipeline is.
We recommend Lock for anything where “5 minutes of degraded behavior on the widget” is not an acceptable failure mode.
7. What’s the security model for our API key?
The api-key attribute is public by design. It sits in the HTML source and is visible to anyone who views your page.
What stops abuse: the API key is origin-locked at api.podsaid.com. Each key has an allowlist of origins (your production domain, staging, localhost during development). A request from an origin that’s not on the list gets a 403, regardless of whether the key value is correct.
- Key format:
pk_live_<48-hex>. - Comparison uses
CryptographicOperations.FixedTimeEquals— constant-time, no timing oracle. - Copy-paste attack (someone lifts your key and uses it on their own site) → 403 from the origin check.
- Rotating a key: request via console. The old key keeps working during a grace window; you swap the HTML, then we retire the old one.
If you need to gate access behind login (i.e. the widget should only run for authenticated users), that’s a page-level concern — put the <ask-pod-widget> tag on an authenticated route. The widget itself has no notion of end-user identity.
8. What network calls happen at runtime? What data leaves the browser?
At mount: no calls. The bundle initializes silently.
On user action (search query submitted, chat message sent):
POST https://api.podsaid.com/...with the user’s query string, thefeed-id, and theapi-key.- Response is streamed back (chat) or returned as JSON (search).
That’s the whole network surface. No analytics beacons, no telemetry to a third party, no session/tracking cookies set by the widget. The only PII that leaves the browser is whatever the user typed into the input. Your privacy policy should mention that queries are sent to api.podsaid.com.
9. Does it work with our framework?
Yes — it’s a standards-compliant custom element, so the embed pattern is the same everywhere. Framework specifics:
- Plain HTML / WordPress: drop the two tags into your template/theme. Done.
- Next.js (App Router): put the
<script>inapp/layout.tsxusingnext/scriptwithstrategy="afterInteractive"(orbeforeInteractiveif you want the element upgraded before hydration). Use the tag directly in JSX — React 19 passes unknown attributes through unchanged. On React 18, usedangerouslySetInnerHTMLor the lowercase attribute names. - SvelteKit: put the
<script>inapp.html. Use the tag directly anywhere in a route or component. - Vue 3: add
ask-pod-widgettocompilerOptions.isCustomElementin your Vite config so Vue doesn’t warn on the unknown element. - Angular: add
CUSTOM_ELEMENTS_SCHEMAto the module (or standalone component) that renders the tag.
TypeScript types for the custom element are not published to npm today; a small .d.ts shim is documented in the install guide.
10. What breaks? Known conflicts, limits, or edge cases?
- YouTube pages (
youtube.com) — YT ships a strict CSP + Trusted Types policy that blocks third-party script injection. The widget won’t run there. If your goal is “let people ask questions from the channel page,” use the PodSaid browser extension, not the embed. This does not affect embedding on your own site. - Multiple instances on one page — supported; each
<ask-pod-widget>tag mounts independently. All share the same bundle load. - Shadow DOM styling — you can’t restyle the widget’s internals from your page CSS. Theming hooks (CSS custom properties on the host element) are the supported extension point.
- Server-side rendering — the custom element renders as an empty box on the server and upgrades on the client. If your SSR framework is strict about unknown elements, see §9.
- Very old browsers — the bundle targets browsers with native custom element and Shadow DOM support (Chrome/Edge ≥ 67, Firefox ≥ 63, Safari ≥ 10.1). No polyfills are shipped.
- Ad blockers — none of the major lists currently block
widget.podsaid.comorapi.podsaid.com. If a user’s blocker interferes, the widget fails closed (the box doesn’t render); no fallback UI today.
11. Can we self-host the bundle?
Technically: yes. Fetch widgets.iife.js from a pinned Lock URL, serve it from your own CDN, and change the <script src="..."> to point at your copy.
Operationally: we don’t recommend it.
- You lose Float updates. Every bugfix becomes a deploy on your side.
- You still need
api.podsaid.comreachable from the browser — self-hosting the script doesn’t remove that. - The origin allowlist on your API key still applies to your host, so there’s no security benefit.
If your ops team’s rule is “no third-party script hosts, ever,” Lock to a pinned version and treat it as a vendored dependency. That gets you the audit trail without a self-hosting pipeline.
12. Do you support SRI (Subresource Integrity)?
Not today. SRI hashes are not published as part of the release.
The pragmatic path if you need SRI-equivalent guarantees: Lock to a pinned version. Lock URLs are immutable and content-addressed — the bytes at /v1.2.3/widgets.iife.js today are the same bytes that will be there next year. You can hash the file yourself and add the integrity attribute manually if your policy requires it:
<script defer
src="https://widget.podsaid.com/v1.2.3/widgets.iife.js"
integrity="sha384-...your-computed-hash..."
crossorigin="anonymous"></script> Published SRI hashes in the release notes are on the roadmap. It’s a legit gap — we’re not going to pretend otherwise.
13. Uptime, SLA, and status page?
Honest state today:
- No public status page at the moment. Roadmap, not shipped.
- No formal SLA on Free / Indie tiers.
- Enterprise SLA is negotiated per contract — talk to us if you need one in writing.
The widget CDN (widget.podsaid.com) sits on Cloudflare’s edge, so its availability tracks Cloudflare’s. The API (api.podsaid.com) runs on a single GCP VM behind Caddy — it’s not multi-region, and we won’t pretend it is. If HA is a requirement for your deployment, tell us up front.
14. Can the widget pull data from other pages on our site — service times, ministry pages, resources?
Not today. The widget is scoped to the sermon archive (feed) associated with your API key. Out-of-feed queries — service times, staff bios, event calendars, ministry pages — return the standard “not covered by the archive” response.
Ingesting arbitrary site content is our most-requested feature and is on the near-term roadmap (delivery this year). Expected shape: an admin flow that lets a customer register additional content sources (sitemap-based crawl of a whitelisted domain, or a structured JSON feed), which gets chunked, embedded, and merged into the same vector index the widget already queries. Same origin lockdown and per-feed scoping as sermon content.
If you’re working on the customer’s site integration and would like early access to the pilot — sitemap format, refresh cadence, per-page metadata schema — reach out. This is the feature we most want real production integrators to help shape.
15. Can we see the questions being asked through the widget?
Yes — every query submitted through the widget appears in your console dashboard.
Per-query, you see:
- The exact query text the user typed.
- Timestamp.
- The sermon citations the API returned in reply (or an empty-result marker if the archive didn’t cover it).
What isn’t captured (by design):
- No visitor identity is attached from the widget — no cookies planted, no session ID, no fingerprint. See §8 for the network surface. Nothing ties two queries from the same browser to a common identity.
- No IP address in the customer-facing dashboard (retained in server logs for abuse investigation on our side only).
Useful for: tuning feed coverage (which queries produce weak matches?), diagnosing empty-result queries, spotting content gaps the archive doesn’t yet cover.
Where to dig deeper
- Install guides (framework-by-framework), attribute reference, and TypeScript shim: PodSaid docs site (link in your onboarding email).
- Roadmap items called out above (SRI publication, smart Lock, public status page): tracked on the platform roadmap.
- Security questions the FAQ doesn’t cover: mail [email protected].