PKCE code challenge failing with 400 on CXone auth endpoint

Getting a 400 Bad Request on the token exchange step. The authorization code works fine, but the server rejects the code_verifier.

Using standard S256 challenge generation:

const challenge = await generateCodeChallenge(verifier);

The initial /authorize call succeeds. The redirect brings back the code. But when I POST to /oauth/token, the response is:
{"error": "invalid_grant", "error_description": "Code challenge mismatch"}

Checked the base64url encoding. It looks correct. Anyone hit this with the CXone endpoints specifically?