PKCE Code Verifier mismatch on Genesys Cloud OAuth callback

We are building a new single-page application for agent desktop extensions. The requirement is to use the Authorization Code flow with PKCE instead of the implicit grant. I have the frontend generating the code verifier and challenge correctly using the S256 method. The initial request to https://login.mypurecloud.com/as/authorization.oauth2 works fine and redirects back with the authorization code.

The problem happens when exchanging the code for tokens. I am sending a POST request to /as/token.oauth2 with the code, client_id, redirect_uri, grant_type, and code_verifier. The response is a 400 Bad Request. The error JSON says "error": "invalid_grant" and "error_description": "The code_verifier does not match the code challenge".

I am storing the code_verifier in session storage before the redirect and reading it back after. The value looks identical. Here is the payload structure I am sending:

{
 "grant_type": "authorization_code",
 "code": "abc123...",
 "redirect_uri": "https://my-app.local/callback",
 "client_id": "my-client-id",
 "code_verifier": "long-random-string"
}

I have checked the character encoding. I am not adding any extra whitespace. The base64url encoding in the challenge generation seems correct. Is there a specific format requirement for the code_verifier in the token request that I am missing? The docs just say it must match. I have tried URL encoding the verifier in the POST body but that made it worse. The flow works perfectly in Postman with the same values. Something about the SPA context must be different.