Content-Security-Policy
If your site sends a Content-Security-Policy header, these are the directives the widget bundle needs, and, just as usefully, the ones it provably does not. A CSP failure is the worst class of embed bug because it is silent: the widget either does not appear or appears unstyled, with a console message most people would never connect to us.
# The minimum for a page with a live oddsmatcher on it
Content-Security-Policy:
script-src 'self' https://oddsrelay.io;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://api.oddsrelay.io;
# Calculator-only pages need no connect-src entry for us:
# the calculators make no network request at all.
#
# 'unsafe-inline' only matters on engines without adoptedStyleSheets.
# Current browsers use a constructed stylesheet, which CSP does not check.Directive by directive
| Directive | Value | Status | Why |
|---|---|---|---|
| script-src | https://oddsrelay.io | Required | The bundle itself. One file, one origin, loaded once per page however many widgets are on it. |
| style-src | 'unsafe-inline' | Legacy browsers only | On every current browser the widget styles its shadow root with a constructed stylesheet, which CSP does not check, so you can leave this out. It is listed because the runtime keeps a <style> fallback for engines without adoptedStyleSheets, and on those a policy without 'unsafe-inline' renders the widget completely unstyled. If your policy sets a nonce, the nonce makes any 'unsafe-inline' fallback be ignored, so this is the one directive a strict-CSP host has to think about. Nothing leaks out of the shadow root, so the permission is scoped to a stylesheet that cannot touch your page. |
| connect-src | https://api.oddsrelay.io | Live board only | Only the oddsmatcher calls the feed, and it calls it from the visitor's browser directly. The calculators make no network request at all, so a calculator-only page needs nothing here. |
| img-src | not needed | Not needed | Every icon is inline SVG and every bookmaker mark is a text monogram. The bundle loads no image file and no data: URI. |
| font-src | not needed | Not needed | The widget uses font stacks only and never downloads a webfont, so it adds no font weight and makes no request to a font CDN. |
| frame-src | not needed | Not needed | The widget renders inline in your page inside a shadow root. There is no iframe anywhere in the bundle. |
| 'unsafe-eval' | not needed | Not needed | No eval, no new Function, no dynamic code generation. The bundle is a plain IIFE. |
Why style-src is on the list
The widget styles itself inside its own shadow root. On every current browser it now does that with a constructed stylesheet, which the CSSOM exempts from this check, so style-src is not consulted at all. If your visitors are all on current browsers, you can drop 'unsafe-inline' and the widget renders normally.
It stays on this list because the runtime keeps a <style> fallback for engines without adoptedStyleSheets, and CSP is set per page, not per browser. On those engines a policy without 'unsafe-inline' leaves a rendered but completely unstyled widget: stacked plain text where the card and table should be. We would rather you grant one permission you may not need than debug that. The permission is also narrower than it looks: everything the stylesheet applies to lives inside the shadow root, so it cannot reach any element of your own page.
Symptoms: what you see, and what is blocking it
Start here rather than with the directive list. Each row is what the browser actually shows an embedder, because the symptom is the only thing you have when the widget is broken on a page you did not build.
| What you see | Console message | Fix |
|---|---|---|
| The widget renders, but unstyled: stacked plain text, no card, no table. | Refused to apply inline style because it violates the following Content Security Policy directive: "style-src …" | An engine without adoptedStyleSheets fell back to a <style> element, and your policy blocks it.Add 'unsafe-inline' to style-src. If you use a nonce-based policy, the nonce disables the 'unsafe-inline' fallback, so add the widget's style through style-src-elem 'unsafe-inline' or contact us before shipping. |
| Nothing renders at all. The host div stays empty and no error appears. | Refused to load the script 'https://oddsrelay.io/widgets/…' because it violates … "script-src …" | The bundle was never allowed to load.Add https://oddsrelay.io to script-src. If there is no console error at all, the script was probably blocked by a consent manager instead, not by CSP. |
| The board shows its reconnecting or empty state permanently. Calculators are fine. | Refused to connect to 'https://api.oddsrelay.io/…' because it violates … "connect-src …" | Only the live board calls the feed; the calculators never do, which is why they look unaffected.Add https://api.oddsrelay.io to connect-src. |
| A JavaScript error appears in your monitoring as the bare string "Script error." with no message or stack. | Script error. | The script tag lost its crossorigin="anonymous" attribute, so the browser reports the error opaquely.Restore crossorigin="anonymous" on the script tag. The bundle is served with Access-Control-Allow-Origin: *, so it costs nothing. |
If none of the above matches
A widget that does not render with no console message at all is usually not a CSP problem. The two common causes are a consent manager that blocked the script before it ran, and a host element that was added to the page after the initial scan.
See consent managers and the troubleshooting matrix.