HubSpot authentication token exchange fails for unauthenticated users but succeeds for logged-in users

I’m working on implementing HubSpot’s OAuth authentication in my web app. Everything works great when users are already signed into their HubSpot accounts, but I run into problems when they need to log in first.

When a user who isn’t logged in tries to connect, they get redirected to HubSpot’s login page in a popup window. After they enter their credentials and complete the login, the token exchange doesn’t work properly. The weird thing is that the exact same process works perfectly for users who are already authenticated.

Here’s my implementation:

const authResponse = await fetch('https://api.hubapi.com/oauth/v1/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: requestData.toString()
});

const authData = { ...authResponse.data, authCode };
console.log('Auth data received:', authData);

return res.status(200).send(
  `<html>
    <head><title>HubSpot Connection</title></head>
    <body>
      <h1>Successfully Connected to HubSpot!</h1>
      <p>This window will close automatically.</p>
      <button onclick="window.close()">Close Window</button>
      <script>
        console.log(window.opener);
        if (window.opener) {
          window.opener.postMessage(${JSON.stringify(authData)}, "http://localhost:3000/settings");
        }
        console.log(window.location);
      </script>
    </body>
  </html>`
);

I’ve tried several things to debug this:

  • Verified that the token exchange request is actually being sent
  • Confirmed that identical requests work for pre-authenticated users
  • Reviewed HubSpot’s OAuth docs and verified my redirect URLs and permissions
  • Found that tokens are generated but the popup-to-parent communication breaks

Any ideas what might be causing this difference in behavior?

This usually happens because HubSpot handles redirects differently after fresh logins. When users authenticate through the popup, HubSpot sometimes changes the redirect URL or adds extra query parameters that weren’t there before. I’ve seen this where the auth code format varies between fresh logins and existing sessions. Make sure your authCode extraction can handle different callback URL structures. Timing’s another issue - fresh logins take longer to complete, so the popup might try to communicate before your parent window is ready. Try adding a polling mechanism that listens for the postMessage over a longer period instead of expecting immediate delivery.

I’ve hit this exact issue with OAuth flows. It’s usually session cookies and third-party cookie policies causing problems. When users authenticate through the popup, HubSpot sets session cookies that your main window can’t access properly - thanks to SameSite restrictions or cross-origin policies. Add credentials: 'include' to your fetch request and double-check your HubSpot app settings have the right domain whitelist. Also make sure the popup URL matches exactly what you registered in HubSpot. Even tiny differences in subdomain or protocol will break token exchange. Fresh logins create different cookie contexts than existing sessions - that’s why it works for users already logged in.

sounds like a popup blocker issue. fresh logins might be handled differently by browsers compared to popups from logged in sessions. try adding window.focus() before your postMessage call and verify if window.opener exists - maybe consider a timeout fallback too.