> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useparagon.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth popup is blocked when connecting an integration

> Browsers block window.open once the click gesture expires. Keep awaits out of the Connect handler.

Breadcrumb

If the OAuth window never appears after Connect, the browser likely blocked `window.open`. Browsers only allow that call while the user's click is still considered active. Anything that delays the popup until after that gesture expires can make the browser treat it as unsolicited.

When this happens, you can expect an `OAuthBlockedError` in the `onError` callback of `paragon.connect`. Then look for work that runs between the click and `window.open`, so `installFlow.start` (or `connect` / `installIntegration`) still happens in the click handler.

## Symptoms

* Clicking Connect does nothing, or a spinner never ends.
* Chrome or Safari shows a blocked-popup indicator.
* Connect SDK `onError` receives:

```javascript theme={null}
{
  name: "OAuthBlockedError",
  message: "Popup was blocked by the browser.",
  meta: null
}
```

## Cause

The OAuth prompt uses a popup. Chrome and Safari allow `window.open` when it runs in the same turn as a user gesture. They block it when the gesture has expired.

The delay can come from several places. One of them is calling `paragon.authenticate` inside the click handler. Others include lazy-loading the SDK on click, awaiting a JWT, analytics, or a slow network after the click. A handler that stacks several of those steps looks like this:

```javascript theme={null}
button.onclick = async () => {
  const { paragon } = await import("@useparagon/connect");
  await paragon.authenticate(projectId, userToken);
  paragon.setHeadless(true);
  paragon.installFlow.start("salesforce", { onError, onNext, onComplete });
};
```

That sequence is: click, then load the SDK, then authenticate, then open OAuth. The last step is no longer attached to the click. Any one of those awaits is enough to lose the gesture.

| Common scenarios | Outcome |
| - | - |
| Dynamic import of `@useparagon/connect` on click | Often blocked |
| `await paragon.authenticate(...)` on click (one possible cause) | Often blocked |
| Analytics, feature flags, or extra network calls before start | Often blocked |
| Slow network after click (you can reproduce this with DevTools throttling) | Often blocked |

## Fix

### 1. Confirm it is a popup block

In a Headless Connect Portal, inspect `error.name` in `onError`. If it is `OAuthBlockedError`, this article applies.

If you are using the hosted Connect Portal, check the browser's popup indicator on your app origin.

### 2. Keep the Connect click free of awaits

Start the install in the click handler with no async work in front of it. If you currently authenticate on click, move `paragon.authenticate` to page load or app start instead. [Displaying the Connect Portal](https://docs.useparagon.com/getting-started/displaying-the-connect-portal.md) already recommends that for token freshness. It also happens to keep authenticate off the click path.

```javascript theme={null}
// Page load, not the Connect click
await paragon.authenticate(projectId, userToken);
paragon.setHeadless(true);

// Connect click: no awaits before start
connectButton.addEventListener("click", () => {
  paragon.installFlow.start("salesforce", {
    onNext,
    onComplete,
    onError: (error) => {
      if (error.name === "OAuthBlockedError") {
        // Tell the user the popup was blocked, and that they can retry after allowing popups for your origin.
      }
    },
  });
});
```

Apply the same rule to other delays: load `@useparagon/connect` before the click, and do not await analytics or a JWT fetch before `start` / `connect` / `installIntegration`.

Keep `onNext` handlers that collect pre-OAuth inputs synchronous with the next user click. Calling `setPreOptions` from that second click is a new gesture, so the OAuth popup that follows it is still allowed.

### 3. Surface the error instead of spinning

`OAuthBlockedError` is thrown so you can replace an infinite spinner with a retry message.

## Related articles

* [Headless Connect Portal](https://docs.useparagon.com/connect-portal/headless-connect-portal.md)
* [installFlow.start](https://docs.useparagon.com/apis/api-reference.md#installflowstart)
* [Displaying the Connect Portal](https://docs.useparagon.com/getting-started/displaying-the-connect-portal.md)
