Issue in Signed Event Webhook - PHP
Issue Summary
I have followed the steps given in https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features/ for PHP and used library mentioned in https://github.com/sendgrid/sendgrid-php/tree/main/lib/eventwebhook similarly checked unittest https://github.com/sendgrid/sendgrid-php/blob/main/test/unit/EventWebhookTest.php for event signature verification
Steps to Reproduce
- Went to https://app.sendgrid.com/settings/mail_settings and did test your integration with the ngrok url.
- Got request with header on ngrok url
- Output from verifySignature is false.
Code Snippet
$timestamp = $req->header("X-Twilio-Email-Event-Webhook-Timestamp");
$signature = $req->header("X-Twilio-Email-Event-Webhook-Signature");
$verifycationKey = "<Verification Key>";
$body = \json_encode($req->all())."\r\n";
\Log::info("+++++++++++++++++++");
\Log::info($body);
\Log::info($timestamp);
\Log::info($signature);
$event = new EventWebhook;
$publicKey = $event->convertPublicKeyToECDSA($verifycationKey);
\Log::info(serialize($publicKey));
$flag =$event->verifySignature($publicKey, $body, $signature, $timestamp);
\Log::info("flag====> ".$flag);
\Log::info("+++++++++++++++++++");
Technical details:
- sendgrid-php version: 7.9
- php version: 7.2
We've seen a similar issue in our nodejs library recently: https://github.com/sendgrid/sendgrid-nodejs/issues/1238
Is this a multi-event webhook? If so, a carriage return and newline (\r\n) needs to be included between every event in the body. Example: https://github.com/sendgrid/sendgrid-nodejs/pull/1271/files#diff-570eee91600a9234a175a0acce452638ee2c7cc583be1a4aa75eb35ca8b9500cR48
Yes we are having multi-event webhook. As per your comment we have modified the code as follows still not luck.
` $timestamp = $req->header("X-Twilio-Email-Event-Webhook-Timestamp"); $signature = $req->header("X-Twilio-Email-Event-Webhook-Signature"); $verifycationKey = "<Verification Key>"; $body = \json_encode($req->all()); $body = str_replace("},{", "},\r\n{",$body)."\r\n"; \Log::info("+++++++++++++++++++"); \Log::info($body); \Log::info($timestamp); \Log::info($signature);
$event = new EventWebhook;
$publicKey = $event->convertPublicKeyToECDSA($verifycationKey); \Log::info(serialize($publicKey)); $flag =$event->verifySignature($publicKey, $body, $signature, $timestamp); \Log::info("flag====> ".$flag); \Log::info("+++++++++++++++++++"); `
@iprashant have you tried with the raw body?
$body = $request->getContent()
$body = str_replace("},{", "},\r\n{",$body)."\r\n";
Hi Shwetha,
I have used PHP file_get_contents('php://input'); still no luck.
Thanks,
OK, we have an internal ticket to investigate the multi-event webhook scenario. Marking this as a bug in the meantime.
This issue has been added to our internal backlog to be prioritized. Pull requests and +1s on the issue summary will help it move up the backlog.
This replacement worked for me on multi-event payloads instead of the one above:
$bodyContent = str_replace(
"},\n{", // The raw string comes in with newlines in this spot
"},\r\n{",
$body // $request->getContent() can be used here for Laravel apps
)."\r\n";
Thank you for taking the time to share your solution @peter-at-bpt!
Hey there, we at fonQ are running into the same issue with the signature verification and the solution of @peter-at-bpt unfortunately does not seem to work. What is the status of your internal investigation?
webhook signed webhook getting false for function = isValidSignature
$publicKey = 'my key';
$input = '[{"DRUPAL_ROOT":"/home/gems2/git/harmony","OURHTTP_HOST":"gemslocal2.artofliving.org","OURREQUEST_URI":"/ca/civicrm/event/register","contact_id":"24495","countryCode":"ca","email":"[email protected]","event":"processed","newHash":"87858ab1e574e0be","newJobId":"39003277","newQueueId":11312,"newemail_id":"13842","send_at":0,"sg_event_id":"cHJvY2Vzc2VkLTIyOTY4OTQyLWlhcXlDLWhlUW5TRnc4LWtsX0dTOWctMA","sg_message_id":"iaqyC-heQnSFw8-kl_GS9g.filterdrecv-7489454b79-khx4b-1-611E028F-2B.0","smtp-id":"iaqyC-heQnSFw8-kl_GS9g@geopod-ismtpd-6-0","timestamp":1629356687,"uniqueParam":"b.39003277.11312.87858ab1e574e0be@newsletter.india.artofliving.org"},{"DRUPAL_ROOT":"/home/gems2/git/harmony","OURHTTP_HOST":"gemslocal2.artofliving.org","OURREQUEST_URI":"/ca/civicrm/event/register","contact_id":"24495","countryCode":"ca","email":"[email protected]","event":"delivered","ip":"50.31.63.175","newHash":"87858ab1e574e0be","newJobId":"39003277","newQueueId":11312,"newemail_id":"13842","response":"250 mail saved","sg_event_id":"ZGVsaXZlcmVkLTAtMjI5Njg5NDItaWFxeUMtaGVRblNGdzgta2xfR1M5Zy0w","sg_message_id":"iaqyC-heQnSFw8-kl_GS9g.filterdrecv-7489454b79-khx4b-1-611E028F-2B.0","smtp-id":"iaqyC-heQnSFw8-kl_GS9g@geopod-ismtpd-6-0","timestamp":1629356688,"tls":0,"uniqueParam":"b.39003277.11312.87858ab1e574e0be@newsletter.india.artofliving.org"}]';
$headerStringValue = $_SERVER['HTTP_X_TWILIO_EMAIL_EVENT_WEBHOOK_SIGNATURE'];
$timestamp = $_SERVER['HTTP_X_TWILIO_EMAIL_EVENT_WEBHOOK_TIMESTAMP'];
and i am calling function like below
$result = isValidSignature($publicKey,$input,$headerStringValue,$timestamp);
i am getting result as false- but all values i am passing fine and also php library also fine.
help me to proceed , whats wrong in above code or any other setting i need to enable?
i have tested in postman by passing these details.
when i debugged - i am getting false from this function :
$success = openssl_verify($message, $signature->toDer(), $publicKey->openSslPublicKey, OPENSSL_ALGO_SHA256);
here its false , please help