I have searched all around the forums as well as Stack Overflow and a handful of other smaller development sites but had no luck, so I guess it is time to make a thread myself.
I’m trying to connect the EventSub Webhook system up to my existing application. I’ve followed through the setup documentation time and time again, as well as tested this with the Twitch CLI.
I can create webhooks and the server responds with the challenge just fine, the webhooks are marked as enabled after the server responds with the challenge and Twitch begin to post to my webhook URL everytime an event happens.
The problem happens when an incoming event is posted to our webhook and we follow the procedure for verifying the event
Testing our work using the Twitch CLI, everything looks all and above board
-> twitch event trigger channel.update -F {our-callback-url} -s {secret}
✔ Request Sent. Received Status Code: 204
✔ Server Said:
However, the second this goes to our production servers, the sha256 hashes are completely different to one another, meaning something somewhere differs…
I’ve cross examined everything I can think of:
The secret we send to Twitch
Ensuring the secret is used as part of the sha256 salt
Validated that both the secret used in the creation of the webhook is the same as the secret used for hashing
Technical:
Server is PHP
Framework is Laravel
public static function verifySecret(Request $request): bool
{
$secret = self::getEncryptedSecret(); // Returns our secret we sent on creation of the webhook
$messageId = $request->header('Twitch-Eventsub-Message-Id');
if ($messageId === null) {
return false;
}
$messageTimestamp = $request->header('Twitch-Eventsub-Message-Timestamp');
if ($messageTimestamp === null) {
return false;
}
$body = json_encode($request->post());
$twitchHmac = $request->header('Twitch-Eventsub-Message-Signature');
$raw = "{$messageId}{$messageTimestamp}{$body}";
$ourHmac = hash_hmac('sha256', $raw, $secret);
$ourHmac = "sha256={$ourHmac}";
return hash_equals($twitchHmac, $ourHmac); // Returns True on Local | False on Production
}
I have two suspicions:
I test on a Mac OS but the server is Linux and unsure if I should be handling this hashing differently?
The timestamp of the event from Twitch is always about 1-2 seconds before the current time, I’m unsure if this is an issue?
You probably should be using the all lower case version of the header names
PHP (generally) converts all the key names of inbounde header to lower case. So I anticipate that $request->header('UPPPERCASE') or $request->header('PascalCase') is resulting in a blank response instead of the expected value.
Hence the generation of invalid SHAs for comparison.
So debug/test this out to see what laravel is returning for header values if PascalCase fetching a header actually works as expected or not.
Additionally:
$body = json_encode($request->post());
Use the RAW body not a rencoded JSON payload. Otherwise any payload that has emoji’s in will always fail or you’ll get other weirdness, as Twitch calculates their has using the RAW payload, but you are caclulating the comparisons using a decoded and reencoded payload which could differ
So I did actually suspect this as I see with the Javascript example, they had to call toLowerCase() on each of the headers.
I decided to place a low level logger into the function so I could check a small monolog file to see if in fact we were getting anything returned from using uppercase letters with the headers.
As you can see, both messageId and messageTimestamp exist in that log, so whether Laravel deep down lowercase this with the method header() or not, it doesn’t seem this is the problem
EDIT:
I just seen your own edit about the payload being re-encoded. Interesting theory… Let me try a few things on that!