The button does nothing
Check the console first — the loader logs rather than throws on the declarative path.
Nothing in the console at all? The element was probably never bound.
bindInvestButtons
runs on DOMContentLoaded and binds each element once — if your button is rendered
afterwards, call window.Cesto.bindInvestButtons({ apiKey }) again. It’s idempotent and
returns the number of elements newly bound.
The popup was blocked
invest() opens a window synchronously on the popup path. Browsers only allow that inside
a user gesture, so this breaks it:
Callbacks never fire (popup mode)
Check forCross-Origin-Opener-Policy: same-origin on the embedding page. It severs
window.opener, so the invest completes but nothing reports back.
Serve the page with same-origin-allow-popups (or no COOP header), or switch to dialog
mode, which is unaffected. Details: COOP and popup mode.
onVerified never fires
The origin isn’t on your key’s allowlist. The flow still works — it’s just unbranded and
unattributed, which is usually what someone means when they say “it works but it looks
wrong”.
Two things to check:
- The exact origin, including scheme, host, and port.
https://www.example.comandhttps://example.comare different origins. - Preview and staging deployments — a Vercel preview URL changes per deployment and won’t be on the list.
The dialog never appears
The overlay swaps to a “Continue in new window” panel after 20 seconds if the iframe doesn’t become ready. Common causes: a privacy extension blocking third-party frames, a network failure, or a browser that won’t frame the app. The failure is remembered for the rest of the tab session (sessionStorage key
cesto-invest:force-popup), so later calls go straight to popup mode. Clear the tab’s
session storage — or open a new tab — while debugging, or you’ll keep testing the popup
path by accident.
The button renders unstyled (React)
A strictstyle-src without 'unsafe-inline' blocks the runtime stylesheet injection.
Import the stylesheet and mark the document instead:
The theme is ignored
Values are validated server-side, field by field, and anything invalid silently falls back to the Cesto default. Check the format before the wiring:- Colors must be
#rrggbb. Not#rgb, notrgb(), notoklch(), notrebeccapurple. radiusmust be a CSS length, and is capped at32px/2rem.schememust be exactlydark,light, orauto.
oklch
resolves to oklch(...) and gets dropped. See Theming.
The user is asked to sign in again
Expected in dialog mode. The iframe runs with partitioned storage, so a session onapp.cesto.co is invisible inside the frame. It is a browser guarantee, not something the
SDK can opt out of. Popup mode shares the app session.
A wallet prompt appears over my site
Also expected, and also dialog mode. Wallet extensions don’t inject providers into cross-origin iframes, so the SDK relays detect / connect / sign requests from the frame on your top-level page. Your page can only add signatures — it can never alter what is being signed. See the wallet relay.onClose fired — did the invest fail?
No. onClose means the surface was dismissed. It also fires after a success. Only
onError means the invest failed, and only onPositionClosed means a position was closed.
See Events.
Server-side rendering errors
@cesto/react is client-only and ships a 'use client' banner, so importing it from a
server component is fine — but the callbacks you pass are functions and cannot cross the
server boundary. Move any component that passes callbacks into a 'use client' file. See
Next.js App Router.
In a non-browser environment the React bindings fire onError with code: 'config_error'
rather than throwing.
Testing against a local app
1
Run the Cesto app locally
On
http://localhost:3000.2
Point the loader at it
new Cesto({ apiKey, baseUrl: 'http://localhost:3000' }), or
data-cesto-base-url="http://localhost:3000" on the button.3
Serve your test page from an allowlisted origin
For example
http://localhost:8787. The origin has to be on the key’s allowlist or
verification won’t fire.4
Watch the events
onReady (frame or popup booted), then onVerified once the backend validates the key
and origin, then the invest lifecycle events.