CORS 403 on Messenger widget init in Next.js 14

Trying to embed the Genesys Cloud Messenger widget into a Next.js 14 app using the App Router. The standard iframe approach works fine in a static HTML test page, but when I drop the script tag into a client component in Next, the browser console throws a CORS error immediately upon initialization. The request to https://api.mypurecloud.com/api/v2/messenger/config returns a 403 Forbidden, claiming the Origin header is not allowed. I’ve whitelisted the domain in the Admin UI under Messaging > Web Messaging > Allowed Origins, and it’s definitely there. The error persists regardless of whether I use next/script or a simple useEffect hook to append the script. Here’s the setup:

'use client';
import { useEffect } from 'react';

export default function MessengerWidget() {
 useEffect(() => {
 const script = document.createElement('script');
 script.src = 'https://static.mypurecloud.com/js/gc-messenger-2.0.0.js';
 script.async = true;
 script.onload = () => {
 window.GC_Messenger.init({
 organizationId: 'my-org-id',
 deploymentId: 'my-deployment-id',
 locale: 'en-US'
 });
 };
 document.body.appendChild(script);
 }, []);
 return null;
}

The network tab shows the preflight OPTIONS request failing. I don’t see any specific config in the SDK docs for handling Next.js hydration quirks or custom CORS headers for the widget itself. We’re not using a custom domain for the messenger yet, just the default purecloud.com endpoints. Anyone else hit this with Next.js? The widget just spins indefinitely because it can’t fetch the config. I’ve tried adding a proxy in next.config.js but that feels like a hack for a client-side widget. The 403 response body is empty, which isn’t helpful. I’m stuck on why the Admin UI whitelist isn’t catching this request.

You’re hitting a classic Next.js hydration clash. The Messenger SDK tries to fetch the config during server-side rendering, but the server doesn’t send the Origin header, so Genesys Cloud rejects it with a 403. The fix is to ensure the script only loads on the client side after the initial render.

Don’t put the <script> tag directly in your JSX. Use a custom hook to inject it dynamically. Here’s a pattern that works:

'use client';
import { useEffect } from 'react';

export default function MessengerWidget() {
 useEffect(() => {
 const script = document.createElement('script');
 script.src = 'https://api.mypurecloud.com/api/v2/messenger/sdk.js';
 script.async = true;
 document.body.appendChild(script);
 
 return () => {
 document.body.removeChild(script);
 };
 }, []);

 return null;
}

This bypasses the SSR CORS check entirely. Also, double-check your Allowed Origins list in Admin > Messaging > Messenger. It needs to match the exact protocol and port if you’re running Next.js locally on port 3000. Mismatched ports cause silent 403s.