PKCE Code Verifier Mismatch on Genesys OAuth Token Exchange

Hey everyone,

I’m trying to implement the Authorization Code flow with PKCE for a single-page application that pulls WFM adherence data. We’re moving away from the implicit grant because the security team is pushing for better practices. I’ve got the initial authorization request working fine, and the browser redirects back with the code, but the token exchange is failing with a 400 Bad Request.

Here’s the sequence:

  1. Generate code_verifier and code_challenge using SHA-256.
  2. Send the user to https://api.mypurecloud.com/oauth/authorize with the code_challenge.
  3. Receive the code in the callback.
  4. Post to https://api.mypurecloud.com/oauth/token to get the access token.

The error response is pretty generic:

{
 "error": "invalid_grant",
 "error_description": "Bad verification code"
}

I’ve double-checked the code_verifier being sent in the POST body. It matches exactly what was used to generate the challenge. I’m using plain fetch in the browser. Here’s the payload I’m sending:

const tokenPayload = new URLSearchParams({
 grant_type: 'authorization_code',
 code: urlParams.get('code'),
 code_verifier: storedCodeVerifier,
 redirect_uri: 'http://localhost:3000/callback'
});

fetch('https://api.mypurecloud.com/oauth/token', {
 method: 'POST',
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded',
 'Authorization': 'Basic ' + btoa(clientId + ':' + clientSecret)
 },
 body: tokenPayload
});

The client_id and client_secret are definitely correct since I copied them straight from the developer console. The redirect URI matches exactly. I’m wondering if there’s a specific format issue with the PKCE parameters or if Genesys requires something extra in the headers.

Has anyone else hit this ‘Bad verification code’ error when switching to PKCE? I’m stuck on this step.

The mismatch usually comes from how you generate the verifier. If you’re using crypto.getRandomValues in the browser, you need to base64url encode the raw bytes correctly. Standard base64 uses + and /, but PKCE requires - and _, plus no padding =.

Here is the safe way to do it in JS:

function generateCodeVerifier() {
 const array = new Uint8Array(32);
 crypto.getRandomValues(array);
 return btoa(String.fromCharCode(...array))
 .replace(/\+/g, '-')
 .replace(/\//g, '_')
 .replace(/=+$/, '');
}

Make sure the code_challenge sent in the initial auth request is the SHA256 hash of this exact string, also base64url encoded. If your verifier has padding or wrong characters, the server rejects the exchange. Check your logs for the exact 400 body, it often says invalid_grant due to verifier mismatch.