Guides
Popup timing
Open the checkout popup synchronously, inside the click handler. Any await before that call gets the popup blocked.
This guide continues the quickstart's hand your user a deposit address step. It covers the popup surface. To show checkout inside your own page instead, see embed checkout inline.
Why this matters
Browsers block window.open() unless it runs synchronously inside a real user gesture, such as a click or a key press. If you do anything asynchronous first, such as awaiting your server call that creates the payment intent, the browser no longer treats the call as part of that gesture.
The browser does not throw or warn. This is standard popup-blocker behavior, and every checkout integration works around it the same way.
The correct pattern
Open a blank window synchronously in the click handler. Then pass it to the SDK's handle.openNow(), which sends it to checkout once the mount is ready:
payButton.addEventListener("click", () => {
const win = window.open("", "canopy_checkout", "width=460,height=640");
handle.openNow(win); // navigates the already-open window
});openNow() can run after an await, because the window is already open by then. Only opening the window has to happen before anything asynchronous.
What happens if you get it wrong
If the window opens outside a synchronous gesture, window.open() returns null. It does not throw. openNow() accepts null and emits an error event with a code saying the popup was blocked.
By then the payer has clicked a button that did nothing visible, which is worse than a clear error. Make window.open() the first statement in the click handler.
With surface: auto, the SDK handles this for its own fallback button. The fallback opens the popup from a real click on that button.
Events from the popup
The popup sends events back to the window that opened it. So ready and error reach your onEvent handler on the popup surface, as they do on the iframe. The popup sends to one specific origin, never a wildcard, so it needs to know yours.
It uses your registered embed origins. If you registered none, it uses the referrer your page sends when it opens the window.
If your page sends Referrer-Policy: no-referrer and you registered no embed origin, the popup has no origin to send to. It stays silent, and onEvent receives nothing from it.
To fix this, register an embed origin on the dashboard, or relax the referrer policy for this page. The popup surface does not need an embed origin otherwise.
resize is iframe-only. A popup is its own window and sizes itself.
See also
- Quickstart: the full walkthrough this guide continues.