Skip to content
Centriu
Centriu Synapse

Embeddable Chat Widget Security Automation: The Secret Key Never Reaches the Browser

Putting a chat widget on a public website means putting some kind of key into a `<script>` tag that anyone visiting the page can read in their browser's developer tools — the real question is not whether that key is visible, but what it can actually do once someone copies it. Centriu Synapse's embeddable widget answers that by design: the public `site_key` in the snippet never doubles as the organization's real, secret API key. It resolves entirely server-side, through a multi-layer check — the site itself must exist and be enabled, its own recorded organization must match the deployment actually serving the request (a defense-in-depth check beyond the database row alone), and the API key it is linked to must exist, not be revoked, and not be expired. Any single one of those conditions failing returns the exact same uniform result to the caller, with no signal about which specific check actually failed. A same-origin allowlist is checked twice — once for the lightweight branding call, again for the real chat message — and a per-site, per-visitor-IP rate limit sits on top of, not instead of, the tenant's own AI cost and quota ceiling. And if something does go wrong internally, the widget still answers with a plain, calm apology and an HTTP 200, never an error payload that would hand a prober more information than "something went wrong, try again."
Secret key never leaves the server
Origin allowlist checked twice
Small business owner checking a phone
The public key in the snippet is never the real one.

Why a chat widget snippet is a different threat surface than a server integration

A server-to-server API integration and a `<script>` tag embedded on a public website face fundamentally different threats. A server-side API key lives somewhere only the integrating team can read; a widget's public key lives in HTML that anyone with their browser's developer tools open can copy in seconds. Building a widget as if that difference did not exist — reusing the same kind of secret credential, trusting the browser's own Origin header without a server-side check, or letting one visitor's message volume draw down the same budget as every other customer's support traffic — turns a convenience feature into an open door.

How the underlying problem shows up before you fix it

A widget's embed snippet contains a credential powerful enough that copying it out of the page source would let someone impersonate the integration elsewhere, not just on the intended site.

A disabled or revoked widget keeps working for longer than expected, because nothing re-checks its status once the browser has already loaded the page.

An error response from the widget tells an attacker exactly which security check failed — wrong origin vs. revoked key vs. disabled site — turning a blocked request into a guided probing session.

One visitor sending a flood of messages through the public widget draws down the same AI budget every other customer's conversation shares, with no separate limit specific to that one visitor or that one embedded site.

A widget conversation runs through separate, thinner logic than the "real" integration, so a bug fixed in one code path silently persists in the other.

An internal error in the widget's own backend surfaces as a raw error message or stack trace in the visitor's browser console, handing out more information than a support message ever should.

Why widget security usually gets treated as an afterthought

A chat widget is often built as the last integration surface, after the server-to-server API already works, and it is tempting to simply reuse the same kind of credential and the same trust assumptions — because they already work for the case where the caller is a trusted backend, not an anonymous browser. Distinguishing failure reasons in an error response feels like better developer experience right up until an attacker is the one reading it. And a rate limit or an origin check that only exists at the CORS layer, rather than being re-verified as an actual authorization decision inside the handler, looks identical to a real check in every legitimate test — until someone sends a request that never goes through a real browser's CORS enforcement at all.

How Centriu Synapse secures the embeddable widget

The widget's public `site_key` follows a fixed, validated format and resolves through `resolveWidgetSite()`, a function documented in its own source as never letting the public key double as a real credential. Resolution runs a multi-layer check: the site row must exist and be marked enabled; the site's own recorded `organization_id` must match the organization configured for this specific deployment — a defense-in-depth check that catches a site record somehow pointing at the wrong tenant, beyond what the database row alone would prevent; and the linked API key must exist, must not carry a revocation timestamp, and must not be past its expiration date if one is set. Any single failure among those returns a uniform `null`, which the caller turns into the same generic 403 response regardless of which specific condition actually failed — nothing in the response distinguishes a disabled site from a revoked key from a wrong-organization mismatch. Successful and failed resolutions are cached separately and briefly, with two different time-to-live values: 30 seconds for a successful resolution, so a busy widget conversation is not running two database SELECTs on every single message, and a shorter 10 seconds for a failure, so a site that gets re-enabled or a key that gets replaced starts working again quickly rather than waiting out the longer success TTL. A same-origin allowlist — normalized by stripping a trailing slash and lowercasing the host before comparison — is checked twice: once at the lightweight branding endpoint the widget calls on load, and again at the actual message-sending endpoint, since CORS reflecting the Origin header is a separate, earlier layer from the actual authorization decision made here. Before a message reaches the AI agent, a rate limit keyed to the specific site and the visitor's IP address runs through the same limiter infrastructure used for server-to-server API keys — layered on top of, never instead of, the tenant's own overall AI cost and quota ceiling, which still caps total spend regardless of how the rate limit behaves. Once past all of that, the widget's message runs through the exact same handler used by the authenticated, server-to-server `/v1/messages` path — the same memory, branding, quota and cost logic — so a widget conversation is not a second, thinner implementation carrying its own separate bugs. And if the underlying agent call itself throws an unexpected error, the endpoint still responds with HTTP 200 and a calm, generic apology message rather than an error payload, deliberately withholding information that could help someone probe the endpoint's internals.

What is actually built today

A public `site_key`, in a fixed validated format, that never doubles as the organization's real secret API key — resolution to the linked key happens entirely server-side.

A four-condition resolution check (site exists, site enabled, organization match, linked key valid and unrevoked and unexpired) collapsing to one uniform failure result — no signal to the caller about which specific condition failed.

Two distinct cache time-to-live values for the resolution result — 30 seconds for success, 10 seconds for failure — balancing database load against how quickly a re-enabled site or a rotated key actually takes effect.

A same-origin allowlist, normalized before comparison, enforced independently at both the branding-config call and the real chat-message call.

A per-site, per-visitor-IP rate limit running through the same limiter used for server-to-server API keys, layered on top of the tenant's own separate AI cost and quota ceiling.

The widget's chat message handled by the exact same code path as the authenticated server-to-server integration — same memory, branding, quota and cost logic, not a parallel implementation.

A deliberate HTTP 200 with a generic apology on internal failure, rather than an error payload that could hand a prober information about what went wrong.

A structurally separate mechanism from wave 13's server-to-server API integration page — different credential model, different threat surface, confirmed by reading both source files directly rather than assumed from naming alone.

A copied site key stops working the moment the site is disabled (illustrative scenario, not a real client)

A visitor to a client's public website opens their browser's developer tools out of curiosity and finds the widget's public `site_key` sitting in the page source, exactly as expected — it is meant to be public. They cannot do anything meaningful with it beyond loading the same widget: it is not the organization's real API key, and every request using it is still checked, server-side, against the linked key's own revocation and expiration status on every resolution.

A support lead later disables that widget site entirely, in response to an unrelated support request. A message sent through a still-open copy of the widget page ten seconds after the change is rejected: the failed resolution is cached for only 10 seconds, so within moments every request against that site key returns the same generic 403 regardless of whether the visitor is using an old page, a saved bookmark, or a deliberately copied snippet.

What changes operationally

A public embed snippet stops being a liability just because it is, by necessity, visible to anyone who loads the page — the value that matters (the real API key) never crosses that boundary at all. A disabled site or a revoked key takes effect within seconds rather than staying live off a stale cache. One visitor sending an unusual volume of messages hits its own rate limit before it can meaningfully affect the tenant's shared AI budget. And an internal failure stays an internal failure — the visitor sees a calm apology, not a clue.

When this is not the right fit

A team that needs the widget to behave completely differently from its own authenticated API integration — a separate model, a separate memory scope, a deliberately different behavior set — will not find that division here: the widget deliberately reuses the exact same handler logic as the server-to-server path, by design, precisely so the two never drift apart.

A reused server-side key vs. a public site key that resolves server-side

Reusing the same kind of secret key for a public embed as for a server integration means that key's exposure in a browser is a real credential leak. Centriu Synapse's widget instead uses a public identifier that is deliberately safe to expose, resolving to the real credential only inside the server, behind a multi-layer check that never tells a failed caller which specific condition it tripped.

Related systems

Main system: Centriu Synapse.

What it does NOT do

  • Does not expose the organization's real secret API key to the browser at any point — the widget's public site key resolves to the linked key entirely server-side, confirmed directly in the resolution function's own source.
  • Does not tell a failed caller which specific check failed — a disabled site, a wrong organization match, a revoked key and an expired key all return the exact same uniform 403, by design.
  • Does not treat CORS reflecting the Origin header as the actual authorization decision — the origin allowlist is checked again, independently, inside the handler, at both endpoints that need it.
  • Does not let a public widget conversation bypass the tenant's own overall AI cost and quota ceiling — the per-site, per-IP rate limit here is an added anti-abuse layer on top of that ceiling, never a replacement for it.
  • Does not surface a raw error or stack trace to the visitor on an internal failure — the browser receives a generic apology message and an HTTP 200, not diagnostic information.

Security and governance

Every organization using Centriu Synapse has its own widget sites and linked API keys, resolved and rate-limited independently of every other tenant. The real secret API key never crosses the network to a visitor's browser. Personal data follows Brazil's LGPD (Law No. 13,709/2018). Full detail on access control lives at /governanca.

Pricing and contracting

Available by monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

Can someone steal my real API key by reading the widget's embed code?

No — the public site key visible in the embed snippet never doubles as the real secret API key. It resolves to the linked key entirely server-side; the secret value itself never crosses the network to the browser.

How fast does disabling a widget site actually take effect?

A failed resolution (including a disabled site) is cached for only 10 seconds, so requests against a disabled site start failing within moments, not after a longer success-cache window.

Does the widget run different AI logic than the regular server-to-server integration?

No — a widget message runs through the exact same handler as the authenticated /v1/messages path, including the same memory, branding, quota and cost logic.

What stops someone from embedding my widget's site key on a completely different website?

A same-origin allowlist, checked independently at both the branding and the message endpoints, rejects any request from an origin not on that site's specific list.

Is there a limit on how many messages one visitor can send through the widget?

Yes — a rate limit keyed to the specific site and the visitor's IP address applies on top of the tenant's own overall AI cost and quota ceiling.

What happens if the widget's backend hits an internal error?

The endpoint still responds with HTTP 200 and a generic, calm apology message rather than an error payload — deliberately withholding diagnostic detail from the visitor's browser.

What does Centriu Synapse cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Synapse secures the embeddable widget

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Synapse — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Synapse — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review