Getting `invalid_client` error with Notion OAuth integration

I’m having trouble with Notion OAuth implementation. The authentication flow works perfectly when I test it through Postman, but fails in my actual app with an invalid_client error.

Here’s my current setup:

app
.get("/auth/notion", async (req, res) => { 
    const authURL = `${process.env.NOTION_AUTH_ENDPOINT}?owner=user&client_id=${process.env.CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(process.env.REDIRECT_URL)}`
    res.redirect(authURL)
})
.get("/auth/notion/redirect", async (req, res) => {
    try {
        const {code} = req.query;
        
        if(!code) {
            throw new Error("Authorization code missing")
        }
        
        const tokenRequest = await axios.post('https://api.notion.com/v1/oauth/token', { 
            payload: JSON.stringify({
                "grant_type": "authorization_code",
                "code": code,
                "redirect_uri": "http://localhost:9000/auth/notion/redirect"
            }),
            headers: {
                "Authorization": `Basic ${Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString('base64')}`,
                "Content-Type": "application/json",
            }
        })
        
        const {access_token, bot_id, workspace_id} = tokenRequest.data;
        
        // additional logic here

    } catch(error) {
        if(axios.isAxiosError(error)) {
            console.log(error.response.data, error.response.status)
        }
        return res.status(400).json({error: `Auth failed: ${error}`})
    }
})

I keep getting { error: 'invalid_client' } 401 response. I’ve double-checked my client credentials and redirect URI multiple times. Any ideas what could be wrong?

check ur notion integration settings in the dev portal - the redirect URL might not match what you’re using in your code. also, log the authorization header and verify the base64 encoding works. special chars in secrets can mess up the encoding.

I hit this exact issue a few months ago and it drove me nuts for hours. Your axios request structure is the problem. You’re wrapping your data in a payload property and then trying to JSON.stringify it, but axios wants the data object directly. Change your token request to this: const tokenRequest = await axios.post(‘https://api.notion.com/v1/oauth/token’, { “grant_type”: “authorization_code”, “code”: code, “redirect_uri”: “http://localhost:9000/auth/notion/redirect” }, { headers: { “Authorization”: Basic ${Buffer.from(${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}).toString('base64')}, “Content-Type”: “application/json” } }); See how the headers go in the third parameter as a config object, not inside the data payload? Your structure was messing up the request body, so Notion’s API rejected it as an invalid client request.

Had the exact same error building my Notion integration last year. Your problem isn’t just the axios config - you’re hardcoding the redirect URI in your token exchange instead of using the environment variable. Look at your first route: you use process.env.REDIRECT_URL, but then you hardcode http://localhost:9000/auth/notion/redirect in the token request. Notion rejects this mismatch with invalid_client. Use the same environment variable in both spots. Also check your CLIENT_ID and CLIENT_SECRET for trailing spaces or hidden characters - wasted hours debugging that once. Notion’s OAuth is super strict, so even tiny mismatches kill the validation.