This example might help you
The only difference between mine and yours is that yours is doing
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));
and mine is just
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
But that wouldn’t generate a 404 unless PHP is messing about somewhere. Unless you were getting the 404 with a GET request, it’s unclear if your current error is a 404 or not. (You got a 404 and switched to POST and now getting a different unspecified error, which means it’s your POST field encoding)
I run
$ch = curl_init('https://id.twitch.tv/oauth2/token');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
'client_id' => CLIENT_ID,
'client_secret' => CLIENT_SECRET,
'code' => $_GET['code'],
'grant_type' => 'authorization_code',
'redirect_uri' => REDIRECT_URI
));