Next.js and fintech app
Getting a third party script past the policy a regulated product ships with
Most sites can add a script tag and move on. A financial product usually cannot, because it ships an enforced content security policy that somebody in the security team owns, and that policy is the first thing the browser consults. Whether the widget works here is decided before any of the usual install questions apply, and it is decided by two directives rather than one.
Why this pairing is its own job
The Next.js install guide covers the tag, and the fintech app guide covers what the assistant has to know. What follows is the part that belongs to neither.
- An enforced policy is normal on a regulated product and rare almost everywhere else, so the ordinary install advice assumes a permission the page does not have.
- The script and the conversation panel come in under different directives. The loader is a script. The panel renders in its own frame, which the browser checks separately, so a policy that permits the script and forgets the frame gives you a launcher that opens onto nothing.
- Nonce based policies, which is what a careful team ships, refuse a script tag written by hand in a layout unless the nonce reaches the element.
- The largest block of safely answerable material on a financial site, the fee schedule and the limits table, is exactly the sort of thing built as an interactive component, and a crawler sent to a page that assembles its numbers in the browser receives a shell.
What changes about the install here
<script src="https://cdn.askably.xyz/w.js" data-key="pk_live_YOUR_KEY" defer></script>The tag is the same one on the Next.js guide. Everything below is about where it goes on a fintech app site specifically.
Two directives, one host
Everything the loader needs afterwards is fetched from the same host the tag was served from, so this is one origin appearing in two places rather than a list of vendors to negotiate. It has to be permitted for scripts, and it has to be permitted as a frame source.
That is the whole conversation with your security team, and it is a short one because it names a single host and no wildcards. Bring both directives to that conversation at once. Coming back a week later for the frame directive costs you another review cycle.
A policy using strict-dynamic is worth flagging explicitly, because it governs script loading and has no effect at all on frames. Teams that rely on it sometimes assume it covers everything a trusted script goes on to do, and the frame directive still has to be written out.
A nonce has to reach the tag
If the policy is generated per request with a nonce, a static script element in a layout carries no nonce and is refused. Read the nonce from the request header in the layout and set it on the element so the value that reaches the browser matches the value in the header for that response.
The symptom is unambiguous once you know it: the tag is present in the page source, nothing renders, and the console reports a policy violation naming the directive rather than reporting a refused origin. Those are two different failures with two different fixes and they look identical from the outside.
Report only mode proves nothing
Under a report only header the browser blocks nothing. The widget loads, the panel opens, everything works, and a violation is quietly logged. A team testing in that mode will sign off an install that stops working the day the enforced header ships.
Test against the enforced header on a deployment before you rely on it. Then open the panel rather than just looking at the launcher, because the launcher rendering proves the script directive and says nothing whatever about the frame.
Where the answerable material lives
| Fee and limit tables that assemble themselves in the browser | A currency selector or a calculator that fetches its numbers after the page loads leaves a crawler with headings and no figures. This is the material with the most answerable volume on a financial site, so if the numbers are not in the served HTML, the assistant has nothing to answer the most common safe question with. |
|---|---|
| Per market pages generated from a dynamic route segment | One page per country, all published, all in the sitemap, all describing different fees and different limits for the same product. A crawl takes the lot, and a question about a transfer charge can be answered accurately from the wrong market. Decide which markets are in the corpus and restrict the crawl to those paths. |
| Approved terms and protection wording, uploaded rather than crawled | Compliance approved wording is deliberate, and quoting it exactly is the point. Where it exists as a document rather than as a route in the application, upload it, which also keeps the version that was approved rather than whichever revision the page currently renders. |
| The fraud route, which belongs in the refusal wording and not in the corpus | Material surfaces when a question matches it. A telephone number for an unauthorised transaction has to surface when somebody is frightened and typing badly, which is precisely when a match is least reliable. Put it in the refusal message itself, which is a setting rather than a source. |
The first thing to get right
Deploy the enforced policy to a non production environment, open the panel there, and read the console before touching anything else.
On this pairing the policy is the gate and every other question is behind it. Working through material, corpus scope and refusal wording against a build where the script never ran is a day spent on the wrong end of the problem, and the console tells you which directive is missing in one line.
The failure that belongs to this combination
A launcher that opens onto nothing
The script directive permits the loader, so the launcher renders and the install looks finished. The panel is fetched the first time somebody opens it, in its own frame, which is a separate check against a directive nobody added. The click produces no visible panel.
It is easy to misread as a slow network or a broken click handler, and easy to confirm properly: the console names the frame directive that refused it. That distinguishes it from an allowlist refusal, which names an origin and happens before anything renders at all, and from a nonce failure, which stops the script and leaves no launcher either.
Because the panel loads late rather than on page load, this survives every check that looks at a freshly loaded page. Somebody has to click. Make opening the panel part of whatever verification you sign off, not just seeing the launcher appear.
Before you go live
- The origin the page is served from has to be on the allowlist for that assistant, or nothing renders and the browser console says which origin was refused. An apex domain and its www are two different origins to a browser, so list both, along with any staging or preview host you want it to work on.
- Open the site as a visitor would, on the pages a fintech app visitor actually lands on, and ask it something only your own material could answer. A widget that renders is not the same as a widget that has read anything.
Questions
- Our security team will ask what the script can reach. What is the honest answer?
- It renders a launcher in a closed shadow root and fetches the conversation panel into its own frame, and everything it needs comes from the one host the tag was served from. It answers from material you indexed, so it has no view of any account, balance or transaction and nothing to look one up with.
- Can we self host the script to avoid a third party in the policy?
- Assets are fetched from whatever origin the tag was served from, so a proxy on your own domain needs no extra configuration on the widget side. Whether that satisfies your policy owner is their call, and it is worth asking before you build it, because a single named host is often an easier approval than a proxy somebody now maintains.
- Is it safe to run an assistant on a site under this kind of scrutiny at all?
- It answers only from material you indexed, so if approved wording is what you index, approved wording is what it can produce. The risk that needs managing is not invention from nothing, it is a fluent summary of a page whose careful phrasing was the entire point, which is why quoting beats paraphrasing here and why the refusal has to be written for this setting.
Keep reading
- Installing on Next.jsPut the tag in app/layout.tsx with the framework script component on afterInteractive, or in pages/_document.tsx, and allowlist preview origins.
- Everything for a fintech appFees, limits and identity checks are safe ground. Balances, transactions and anything reading as a personal recommendation are not.
- Keeping the widget on the marketing routes and out of the productThe root layout is the documented place for the tag, and on a subscription product it is also the signed in dashboard. Move it down a level.
- Publishing an article and publishing an answer are the same act hereA published article is a communication in the firm's name. Guide lets any agent publish one, and the corpus keeps it until the next crawl.
- Publishing a financial promotion with a single buttonThe number sits in one element and the sentence that qualifies it in another, and publishing a financial promotion here is one button anyone with access holds.
Try it on your own material
Upload a document or point it at your site, paste one line of HTML, then ask it something only your business could answer.