Back to Blog
embedapi-keyssecuritysaaswidgets

Embeddable AI Widget: API Key Security Done Right

How I built an embeddable AI widget with scoped, revocable API keys and ephemeral session tokens so the real secret never reaches the browser.

By Mike Hodgen

Short on time? Read the simplified version

The Distribution Win That's a Security Minefield

I built an embeddable AI widget so customers of my SaaS could drop lead capture and a voice AI agent onto their own websites with a single script tag. Paste one line, refresh, done. That's the distribution dream. Your software shows up on hundreds of customer sites without you lifting a finger.

Then I sat down to actually build it and realized the embeddable AI widget API key security problem is where most people get this wrong.

What I wanted: one script tag

The goal was dead simple. A customer logs into my dashboard, copies a script tag, and pastes it into their site's footer. The widget loads, captures leads, runs a voice conversation, and pipes everything back into their workspace. No SDK install. No engineering ticket on their end. One line.

That simplicity is the whole product. The moment it takes a developer two days to integrate, half my customers never finish.

Why this is harder than it looks

Here's the problem nobody warns you about. The second your code runs on someone else's domain, your security model breaks.

You cannot ship your real API key to a browser you don't control. Anyone who opens devtools can read it. View source, network tab, done. Your secret is now public.

So I had three problems stacked on top of each other:

  1. The real secret can never reach the browser.
  2. Access has to be scoped per customer, not shared across everyone.
  3. Access has to be revocable instantly when a customer churns or a key leaks.

The rest of this article is the exact pattern I used to solve all three. It's the difference between a demo and software you can actually distribute.

Why You Can Never Ship Your Real API Key to a Customer's Site

Let me make the failure case concrete, because this is the part technical buyers want to see me get right.

The browser is hostile territory

Say you take the lazy route and embed your platform API key directly in embed.js. The file ships to every visitor on every customer site. That key is now visible to anyone who opens the network tab.

A scraper finds it. A competitor finds it. A bored teenager finds it. Now they can call your AI endpoints directly, burn through your model spend, and rack up a bill that lands on you. The browser is not your environment. It's hostile territory, and you have to treat every byte you send there as public.

This is the same principle behind wiring tools without credentials to leak. The real secret stays server-side, always. The client only ever gets something disposable.

One leaked key, every customer exposed

It gets worse if you use one key for all customers. A single leak doesn't compromise one workspace. It compromises everyone. One scraper on one customer's site and your entire tenant base is exposed.

I've seen two naive shortcuts people reach for. The first is proxying every single request through their own backend with the shared key. That works for a while, but you've now made yourself the bottleneck for every interaction on every customer site, and you still haven't solved scoping or revocation.

The second is trusting the originating domain. "Only requests from approved domains work." Domains are trivially spoofable from a server. Origin headers are a defense-in-depth layer, never a primary gate.

The real answer is to never put a usable secret in the browser at all. Give the browser something that identifies the customer and can be exchanged for short-lived access. Nothing more.

Per-Customer Scoped, Revocable Embed Keys

The first layer is issuing each customer a public embed key that is unique to their workspace.

One key per workspace, not per platform

This key is safe-ish to expose, and I say "safe-ish" deliberately. It can't move money. It can't read another workspace. It can't call your AI models directly. On its own it does almost nothing.

All it does is identify the originating workspace and act as a ticket that can be exchanged for a session. Think of it like a coat check tag. The tag itself is worthless. It just proves which coat is yours.

The scoping matters. The key is bound to a single workspace and a permitted set of actions: lead capture and starting a voice session. That's it. Nothing else. Even if someone pulls the key off a customer's site, the blast radius is one workspace and two actions, both of which are heavily constrained downstream.

Revocation that takes effect immediately

Here's the part that lets me sleep. I store every embed key with an active flag and a fast indexed lookup. When a key is presented, the server checks that flag before anything else happens.

Comparison showing a single shared secret exposing all customers on leak versus per-customer scoped keys that limit blast radius and revoke with a one-line database update Shared platform key vs per-customer scoped revocable keys

When a customer churns, I flip one row to inactive. When a customer reports a leaked key, I flip one row and issue them a new one. Their widget stops working everywhere on the next request. No cache to wait out, no propagation delay, no redeployment.

Contrast that with a single shared secret. To revoke a shared secret, you have to rotate it and re-issue it to every customer simultaneously. That's a coordinated migration across your entire base for one leak. Scoped and revocable keys turn a fire drill into a one-line database update.

This is scoped, revocable API keys done the boring, correct way.

The Session Endpoint: Minting Short-Lived Tokens So the Secret Stays Server-Side

This is the heart of the pattern. Everything else hangs off it.

The exchange: embed key in, ephemeral token out

The embed script calls a session endpoint with the public embed key. The server validates the key, is it active, is it scoped to the right actions, and then mints a short-lived token bound to that workspace.

Flow diagram showing a public embed key exchanged at a rate-limited session endpoint for a short-lived workspace-bound token while the real platform secret stays server-side The full token exchange flow: embed key to ephemeral token to server-side secret

The real platform secret never leaves the server. Ever. The browser only ever holds an ephemeral session token that's useless the moment it expires and scoped to exactly one workspace's permitted actions.

I make the token a signed JWT with a workspace claim and a short exp. Minutes, not days. The widget re-mints a new token on demand when the old one expires, so a long voice session stays alive without ever holding a long-lived credential.

Binding, expiry, and what the token can't do

The ephemeral session token widget pattern lives or dies on what the token can't do. Mine carries zero ability to touch another tenant. The workspace is baked into the signed claim, so even if someone grabs a live token, they get a few minutes of access to one workspace's two permitted actions, then it's dead.

The other thing people forget is rate-limiting the session endpoint itself. If a leaked embed key can mint unlimited tokens, you've just moved the problem one layer down. I cap token minting per embed key per minute. A leaked key can't be used to mint tokens at scale, which means it can't be used to spin up a flood of valid sessions.

So the chain looks like this: public embed key (safe to expose) gets exchanged at a rate-limited endpoint for a short-lived, workspace-bound token, while the real secret stays locked behind your server. Three layers, and the browser only ever sees the weakest two.

The Tiny Public embed.js and How the Widget Actually Loads

Now the part the customer actually sees. The one script tag.

What the script tag does

The customer pastes a small script tag with their public embed key as an attribute:

Vertical step sequence showing how a pasted script tag reads the public embed key, fetches an ephemeral token, and mounts the widget inside an isolated iframe or shadow DOM How the embed.js script tag loads the isolated widget

<script src="https://cdn.yoursaas.com/embed.js" data-embed-key="wk_pub_abc123"></script>

That's the entire integration. embed.js reads the key from the attribute, calls the session endpoint, gets back an ephemeral token, and then loads the actual widget UI in an isolated context.

I render the widget inside an iframe or shadow DOM. This does two jobs at once. It stops the host site's CSS from breaking my widget, and it stops the host site from reading my widget's internals or scraping data out of it. Isolation cuts both ways, which is exactly what you want when your code is a guest on someone else's page.

Keeping the public file dumb on purpose

embed.js is intentionally minimal. It contains no secrets and no business logic worth stealing. Read the key, fetch a token, mount the UI. That's the whole job.

Every piece of sensitive work happens behind the session endpoint and the scoped backend. If someone downloads and reverse-engineers embed.js, they learn nothing they couldn't learn by reading my docs. There's no secret sauce in the public file because there's no secret in the public file.

Two more things I bake in. First, I version the embed script behind a stable URL so I can ship fixes and improvements without a single customer re-pasting anything. The script tag they pasted six months ago keeps getting the latest secure build automatically.

Second, CORS and origin checks as a defense-in-depth layer. Not the only defense, I already covered why domain trust isn't a real gate, but it raises the cost of casual abuse and gives me cleaner logs. Layers, not a single wall.

Routing Embedded Interactions Through Scoped, Tenant-Isolated Endpoints

Once the widget is live, every lead submission and voice interaction flows back to my backend. This is where tenant isolation has to be airtight.

Every request carries the workspace claim

Each request from the embedded widget includes the ephemeral token. My backend validates the token and reads the workspace from the signed claim inside it. Never from a query parameter. Never from a header the browser sets. Never from anything the browser can spoof.

This is the single most important design decision in the whole system. The workspace identity comes from a server-signed token, so a malicious user can't change a workspace_id in a request and read someone else's leads. Leads land in the correct workspace because the workspace was decided server-side at token-mint time.

This eliminates an entire class of bug that AI-generated code is notorious for. If you've ever read the kind of access-control bug AI developers never think about, it's exactly this: trusting a client-supplied ID. Derive the tenant from the signed token and the bug can't exist.

Row-level security as the backstop

I don't trust my own endpoint code to be perfect, so I add row-level security bound to the workspace claim as a backstop. Even if I ship an endpoint with a bug that forgets to filter by workspace, RLS at the database layer refuses to return another tenant's rows.

Diagram showing tenant isolation with the workspace derived from a signed token claim as layer one and row-level security at the database as a backstop layer two Two-layer tenant isolation: signed token claim plus row-level security backstop

Two independent layers have to both fail for cross-tenant leakage to happen. Endpoint logic and database policy. That redundancy is the difference between a bug and a breach.

I also log every embed-origin request with its workspace, origin, and token ID. When something looks abusive, a spike in token mints, requests from an unexpected origin, I can see it and act before it becomes a bill or a breach.

What I'd Check Before Letting This Touch a Paying Customer

Here's the checklist I run before any embed touches a paying customer. Use it to evaluate any vendor's embed, or any code your own team ships.

The pre-ship checklist

  • The real platform secret never appears in the browser. Not in embed.js, not in a network response, nowhere.
  • Embed keys are scoped to one workspace and a small set of permitted actions, and they're revocable with a single database update that takes effect on the next request.
  • Session tokens are short-lived (minutes) and bound to the workspace via a signed claim.
  • The session endpoint is rate-limited so a leaked embed key can't mint tokens at scale.
  • The widget loads in an iframe or shadow DOM, isolated from the host page.
  • Every backend endpoint derives the workspace from the token, never from client input, and RLS backstops it at the database.

Vertical six-point pre-ship security checklist for an embeddable AI widget, with a result bar showing all six true means safe to distribute and any one false means it is only a demo The six-point pre-ship security checklist

If all six are true, you can distribute. If any one is false, you have an internal demo wearing a distribution costume.

Where I draw the line

I'll be honest about what this pattern does not solve. A determined customer can still abuse their own quota. They have a valid key and valid tokens, so they can hammer their own widget and run up usage. This design stops cross-tenant attacks and secret leakage. It does not replace usage caps, per-workspace rate limits, and billing-side monitoring. You still need those.

But that's a known, bounded problem you can price for. Unbounded secret exposure across your whole customer base is not. This is the layer that decides whether you sleep at night when your code is running on a thousand sites you don't control. It's the same discipline I cover in the security pass every AI-built SaaS needs, the work that turns demo-ready into distribution-ready.

Ready to bring AI leadership into your company?

I work with a small number of companies at a time. If you're serious about AI, apply to work together and I'll review your application personally.

Apply to Work Together

Get AI insights for business leaders

Practical AI strategy from someone who built the systems — not just studied them. No spam, no fluff.

Ready to automate your growth?

Book a free 30-minute strategy call with Hodgen.AI.

Book a Strategy Call