I am struggling with the EventWebhook signing procedure a while now and can't get it to work. Tried multiple options, including using the raw req body for the timestamp. The verification always fails..
Edit: I am using the verifySignature function from the eventwebhook package here.
Edit 2: Added sample data that was gathered by logging the respective constants.
import express from "express";
import cors from "cors";
import asyncHandler from "express-async-handler";
import createError from "http-errors";
// @ts-ignore
import { Ecdsa, Signature, PublicKey } from '@starkbank/ecdsa';
const app = express();
app.use(cors({ origin: true }));
app.use(
express.json({
verify: (req, res, buf) => {
// @ts-ignore
req.rawBody = buf;
},
})
);
const verifySignature = (publicKey: PublicKey, payload: any, signature: string, timestamp: string) => {
let timestampPayload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
timestampPayload = timestamp + timestampPayload;
const decodedSignature = Signature.fromBase64(signature);
return Ecdsa.verify(timestampPayload, decodedSignature, publicKey);
}
app.post(
"/event",
asyncHandler(async (req, res) => {
const signature = req.get('X-Twilio-Email-Event-Webhook-Signature');
const timestamp = req.get('X-Twilio-Email-Event-Webhook-Timestamp');
const payload = req.body; // req.rawBody also does not work
if (!signature || !timestamp || !payload) {
throw createError(403, "Unauthorized Request");
}
console.log('verifying')
console.log(signature)
console.log(timestamp)
console.log(JSON.stringify(payload))
const publicKey = PublicKey.fromPem(functions.config().env.sendgrid.publicKey);
if(!verifySignature(publicKey, payload, signature, timestamp)) {
throw createError(403, "Unauthorized Request");
}
console.log('verified')
})
);
// @ts-ignore
app.use((error, req, res, _) => {
res.status(error.status || 500);
res.json({
status: error.status,
message: error.message,
});
console.error(error.message)
});
// sample data from logs above
const TIMESTAMP = "1591866114"
const PAYLOAD = [{"email":"[email protected]","event":"bounce","ip":"167.89.17.173","mc_stats":"singlesend","phase_id":"send","reason":"550 5.5.0 Requested action not taken: mailbox unavailable (S2017062302). [VE1EUR03FT058.eop-EUR03.prod.protection.outlook.com]","send_at":"1591866054","sg_event_id":"Ym91bmNlLTAtMTU2MTI0NDktQTVjTHQweTBTWXlBLXN2N3RWNUQydy0x","sg_message_id":"A5cLt0y0SYyA-sv7tV5D2w.filterdrecv-p3iad2-784dbb6bd8-cmnvf-19-5EE1F2DB-11B.1","sg_template_id":"d-bb3d0c9fd0e1494f990a9d386437c6b0","sg_template_name":"Clone: Clone: Clone: Clone: Clone: Clone: Clone: Clone: Clone: Clone: Clone 2020-06-11T08:58:41.387Z","singlesend_id":"bfba94a4-abc1-11ea-b5ff-fac9c7677002","singlesend_name":"DEV-Test 11","smtp-id":"<[email protected]>","status":"5.5.0","template_hash":"030c05c8aa7b8112e5e2d6a99def801d95ccb03f0cf0388874129b4dfb1f41e9","template_id":"d-bb3d0c9fd0e1494f990a9d386437c6b0","template_version_id":"a55a3135-fdf9-41af-8c29-7b93962baab7","timestamp":1591866077,"tls":1,"type":"bounce"}]
const PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETg3hldXJ3S/JlTbsYXdYCDuSpQHISB5C9xPgDkqjjPAyVIVhwSTR7UhIWC7mpvXa4vmCNyAucNmHAosg3p0/eA=="
const SIGNATURE = "MEYCIQCKLxRqhAbX/XlPl35AoiOII6l5D64R21TaiX4TB8sejQIhAJc/PM1SDCkfdyx6gu9Up1GjbJyHz2m6jicfyjaKJupj"
Unauthorized Request
Payload in the sample is an array? Maybe try just passing in the contained object itself.
Thanks for the hint, but it does not work. Your docs also state that the entire payload has to be used.
Thanks for reaching out, I've been able to reproduce this issue using my own account. I've reached out to the owning team for further help and will circle back with an update when I hear more.
Hi @steinroe
I was able to talk to the owning team and determine that the issue here is that a \r\n carriage return is being stripped from the end of the JSON. Here's a snippet of the express server I wrote to process incoming webhooks:
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var EventWebhook = require('@sendgrid/eventwebhook');
app.use(bodyParser.text({ type: 'application/json' }));
var isValidSignature = function(req, res, next) {
let eventWebhook = new EventWebhook();
const signature = req.get('X-Twilio-Email-Event-Webhook-Signature');
const timestamp = req.get('X-Twilio-Email-Event-Webhook-Timestamp');
const ecPublicKey = eventWebhook.convertPublicKeyToECDSA('<your_public_key>');
if (eventWebhook.verifySignature(ecPublicKey, req.body, signature, timestamp)) {
next();
} else {
res.status(400).end();
}
};
app.use(isValidSignature);
app.post('/', function (req, res) {
// do something with the request
res.status(200).end();
});
app.listen(3000);
The key here was using app.use(bodyParser.text({ type: 'application/json' })); as middleware to parse the request.
Hope this helps!
Edit 3 (Final update..). Got live requests working :tada:
Here's what my endpoint looks like.
import * as functions from "firebase-functions";
import * as express from "express";
//NOTE: We are not using body-parser middleware here.
app.post("/sendgrid/webhook", async (req, resp) => {
try {
const signature = req.header("x-twilio-email-event-webhook-signature")!;
const timestamp = req.header("X-Twilio-Email-Event-Webhook-Timestamp")!;
//getConfig is mostly a wrapper to functions.config() to get cloud function configuration json
const key = getConfig().sendgrid.webhook_verification_key
//Because this is a cloud function environment, we can access the original request body (not the parsed payload) on req.rawBody.
//We need to cast to functions.https.Request because `rawBody` is not available on the normal express.Request object.
//the rawBody is of type {Buffer}, so we need to call .toString to convert it into the correct format. Default encoding is `utf8`
const bodyPayload = (req as functions.https.Request).rawBody.toString();
//See note below in "edit 2" on how SecurityService is used, and what the body of the verifySendgrid function looks like.
const verified = await SecurityService.shared.verifySendgrid(bodyPayload, timestamp, signature, key)
logger.info("Sendgrid key verified", verified);
if (!verified) {
resp.sendStatus(403);
return
}
resp.send(204);
} catch (error) {
logger.error("Failed to process webhook", error);
resp.status(500).send(error);
return;
}
})
Edit 2: Ok, some progress. I have a unit test that I can use string values to check signature validation. I pull these values off of the incoming request logs. I'm able to get my tests to pass signature validation in some cases- but as @eshanholtz alludes to, it depends on the format of the request body (not a big surprise there). However, getting the incoming request to format/encode correctly has been a challenge.
A couple notes: I'm using express with Firebase Cloud Functions. It looks like @steinroe is also using Cloud Functions based on the use of functions.config() to get his public key. I mention this because it probably has an effect on how the request body is being encoded/decoded.
Examples of data that does and does not work are below. I'm using the Sendgrid Test event in all cases, triggering it in the Sendgrid UI to my local computer using ngrok.
My validation code lives in a class I'm calling SecurityService and looks like this:
//SecurityService.ts
const EventWebhook = require('@sendgrid/eventwebhook');
...
verifySendgrid(body: string | Buffer |any, timestamp: string, signature: string, validationKey: string): boolean {
const webhook = new EventWebhook();
const publicKey = webhook.convertPublicKeyToECDSA(validationKey);
const verified = webhook.verifySignature(publicKey, body, signature, timestamp);
logger.info("Webhook verified", verified)
return verified;
}
Here are some various data payloads and the result from the validation, as triggered from my unit test
//DOES NOT WORK
const Data = {
signature: "MEUCIEJV9mjasgYQMJLoZTPgHww0laxlXwR8AjAiezlLp3vNAiEAz8Mo5AKH33lnJBt5wcn12gfK2X3hLhfOOV2BkhwHqdg=",
publicKey: publicKey,
timeStamp: "1592435350",
rawBody: "[{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"processed\",\"category\":[\"cat facts\"],\"sg_event_id\":\"G4Kr_WXkNOj7fRyXeUtUGA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"deferred\",\"category\":[\"cat facts\"],\"sg_event_id\":\"nKg8u1NDYXJRXmzaxH1liQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"response\":\"400 try again later\",\"attempt\":\"5\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"delivered\",\"category\":[\"cat facts\"],\"sg_event_id\":\"VSLUsDbr8h4a2Y6KpduYRg==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"response\":\"250 OK\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"open\",\"category\":[\"cat facts\"],\"sg_event_id\":\"Xd2JG8frysTHncXYShy48w==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"click\",\"category\":[\"cat facts\"],\"sg_event_id\":\"hg0SHdJRspW-ZaVI3CyikQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"bounce\",\"category\":[\"cat facts\"],\"sg_event_id\":\"SDgDiMR1k-dm-_rmNkc89A==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"reason\":\"500 unknown recipient\",\"status\":\"5.0.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"dropped\",\"category\":[\"cat facts\"],\"sg_event_id\":\"HnwNZ7xy69KtIjxH4GqL7Q==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"reason\":\"Bounced Address\",\"status\":\"5.0.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"spamreport\",\"category\":[\"cat facts\"],\"sg_event_id\":\"zxvrZ1mzabKL8JgBYaX-hQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"unsubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"UGqgITtUIgHTQSMYiiP6HQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"group_unsubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"79-zmpw4DHgYVD8_IKLlIw==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\",\"asm_group_id\":10},{\"email\":\"[email protected]\",\"timestamp\":1592435149,\"smtp-id\":\"\u003c14c5d75ce93.dfd.64b469@ismtpd-555\u003e\",\"event\":\"group_resubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"ZBpbMNafmDtIGLewzg630g==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\",\"asm_group_id\":10}]\n",
}
//WORKS
const Data2 = {
signature: "MEUCIAIe0rjIvbAUh8OjNJEyVMJI6U7Em2g7+aZZceFfJUFYAiEAnVYYeMe/KYD1Hm6hxjhBfuB6+vNCkYJ2S8oGMITJqRs=",
publicKey: publicKey,
timeStamp: "1592593735",
body: "[{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"processed\",\"category\":[\"cat facts\"],\"sg_event_id\":\"x20UK2XxckmKJ9OXtRq4ug==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"deferred\",\"category\":[\"cat facts\"],\"sg_event_id\":\"LfQ2UcRW7kj5115Ef449hw==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"response\":\"400 try again later\",\"attempt\":\"5\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"delivered\",\"category\":[\"cat facts\"],\"sg_event_id\":\"Ztr5mr9vQDVJPKBUWutsOw==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"response\":\"250 OK\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"open\",\"category\":[\"cat facts\"],\"sg_event_id\":\"SAN0NDMBhqIh2zpXy-Pj4Q==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"click\",\"category\":[\"cat facts\"],\"sg_event_id\":\"HoNq3smS4vKo9UvEu0Cf-g==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"bounce\",\"category\":[\"cat facts\"],\"sg_event_id\":\"sWfKKQDCqQg1uc7PlAbHlg==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"reason\":\"500 unknown recipient\",\"status\":\"5.0.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"dropped\",\"category\":[\"cat facts\"],\"sg_event_id\":\"W2vsdQPLmZnfgNo1Ct9MzA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"reason\":\"Bounced Address\",\"status\":\"5.0.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"spamreport\",\"category\":[\"cat facts\"],\"sg_event_id\":\"Ej-yXMhNr7UvYyhmQd3JQA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"unsubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"ajvu0Zv5k6uRWsgmlW7xuQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"group_unsubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"-b4I2y2bDYe-ysFl9XdY7g==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\",\"asm_group_id\":10},{\"email\":\"[email protected]\",\"timestamp\":1592591453,\"smtp-id\":\"\\u003c14c5d75ce93.dfd.64b469@ismtpd-555\\u003e\",\"event\":\"group_resubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"ZL8SxBrsmF45Uo6mgLBj9A==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\",\"asm_group_id\":10}]\n"
}
//DOES NOT WORK
const Data3 = {
signature: "MEUCIHY4UYzuKHLEORbT7HOEvE7mN7pJ+wDickl/P9Duuhr3AiEAin7zXWziirPsC50gy/mS5ppLLyWD5Pvd7Zw7d+K0JEo=",
publicKey: publicKey,
timeStamp: "1592594548",
body: "[{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"processed\",\"category\":[\"cat facts\"],\"sg_event_id\":\"_GWGyz7BmenNrCvqOzY8xw==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"deferred\",\"category\":[\"cat facts\"],\"sg_event_id\":\"oLjoX_iAZZ4e1VYajkEVnw==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"response\":\"400 try again later\",\"attempt\":\"5\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"delivered\",\"category\":[\"cat facts\"],\"sg_event_id\":\"QSqMEMMMCHKo49KQNirhlA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"response\":\"250 OK\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"open\",\"category\":[\"cat facts\"],\"sg_event_id\":\"MSQIA6RaVjmREBCbQCM4sA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"click\",\"category\":[\"cat facts\"],\"sg_event_id\":\"2-uBjQoOWl587NXVco4EoA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"bounce\",\"category\":[\"cat facts\"],\"sg_event_id\":\"8SY-_eku8e2q_0RNAo74-Q==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"reason\":\"500 unknown recipient\",\"status\":\"5.0.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"dropped\",\"category\":[\"cat facts\"],\"sg_event_id\":\"T88HJGZDMRmGlOyrPmAQiA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"reason\":\"Bounced Address\",\"status\":\"5.0.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"spamreport\",\"category\":[\"cat facts\"],\"sg_event_id\":\"CU0CGQs46sefZKgR22pMkw==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"unsubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"qjGdug6jC4QQOMyjFaTChQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\"},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"group_unsubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"iZ5CFG6-mCeTCmn_aj9yRQ==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\",\"asm_group_id\":10},{\"email\":\"[email protected]\",\"timestamp\":1592593964,\"smtp-id\":\"<14c5d75ce93.dfd.64b469@ismtpd-555>\",\"event\":\"group_resubscribe\",\"category\":[\"cat facts\"],\"sg_event_id\":\"nqwBVclSeqfbxoEqsyfUfA==\",\"sg_message_id\":\"14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0\",\"useragent\":\"Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\",\"ip\":\"255.255.255.255\",\"url\":\"http://www.sendgrid.com/\",\"asm_group_id\":10}]"
}
Edit: actually, my signatures are not verifying. I just was no longer getting an error. I see that the public key is parsing correctly. Digging deeper, it looks like all of the set up values are at least valid-looking (the StarkBank SDK is able to parse public key, signature with values for X,Y,and R,C), but the signature verification comes back as false. I've tried using the bodyParser as suggested, and that doesn't seem to do the trick for me, either.
=== original post ===
First of all, I was able to follow this thread and get my signature validating to work. So, thank you for that!
However, I spent the last 2 days trying to get this event signing to work, and tried a number of libraries for validating these signatures, and none of them worked. I am on node 10, so the example linked in the documentation that references the Node Crypto library wouldn't work for me (as it was only available on node 14).
I tried all of the popular elliptic crypto libraries on NPM, and none of them worked for lots of reasons- unable to parse the signature, unable to parse the public key, etc. I tried all of the different curve options provided by the elliptic library on npm. All failed for different reasons.
None of my googling surfaced the Starkbank library. Even if i found it, i likely wouldn't have trusted it as only been downloaded ~100 times.
I wrote to support, and they attempted to escalate the request - still haven't heard back.
It wasn't until a friend dug out the pull request from @eshanholtz for the eventwebhook package that I was able to find a solution. _THANK YOU_ for that package.
The good news is that using the package, is as able to verify signatures on the first try.
Please, please, please update your documentation/readme to at least _mention_ all of the various SDKs that are available in this monorepo. That small hint would have saved me 2 days of frustration.
First of all, sorry for my late response and thanks for the help @eshanholtz!
Unfortunately, I can agree with @Neilpoulin. Whatever I tried, including your source code, did not work and I always got false back. However, thanks to the code from @Neilpoulin, it finally worked. I guess what was missing in your code @eshanholtz was the toString().
I just ran into this signature verification issue when implementing a new webhook. JSON.stringify(req.body) would always fail validation, but switching to req.rawBody fixed it. +1 on the suggestion to mentionreq.rawBody in the example. And thanks @eshanholtz for building @sendgrid/eventwebhook.
None of my googling surfaced the Starkbank library. Even if i found it, i likely wouldn't have trusted it as only been downloaded ~100 times.
I have to agree with this. After reading the webhook docs online, I resorted to using test/typescript/eventwebhook.ts as a reference implementation because it was contained in the initial PR. After reviewing the eventwebhook source, I initially looked into alternative ecdsa libraries, and then at copying over just the necessary code from starkbank (which was more extensive than I anticipated). The starkbank repo is missing a package-lock.json and relies on 2 packages that can be replaced with native node libraries: js-sha256 and big-integer. It also lists mocha as a non-dev dependency. None of these are earth shattering, but they do make me question how much peer review the repo has received.
Thank you all for your feedback! I have opened PR #1153 to update the interface for the signature verification function to accept both string and Buffer types. The PR also includes documentation for the bodyParser middleware and Firebase Cloud Functions example usages (thanks @Neilpoulin !).
Changes will be included in the next release scheduled for tomorrow, 2020-06-24
Most helpful comment
Edit 3 (Final update..). Got live requests working :tada:
Here's what my endpoint looks like.
Edit 2: Ok, some progress. I have a unit test that I can use string values to check signature validation. I pull these values off of the incoming request logs. I'm able to get my tests to pass signature validation in some cases- but as @eshanholtz alludes to, it depends on the format of the request body (not a big surprise there). However, getting the incoming request to format/encode correctly has been a challenge.
A couple notes: I'm using express with Firebase Cloud Functions. It looks like @steinroe is also using Cloud Functions based on the use of
functions.config()to get his public key. I mention this because it probably has an effect on how the request body is being encoded/decoded.Examples of data that does and does not work are below. I'm using the Sendgrid Test event in all cases, triggering it in the Sendgrid UI to my local computer using ngrok.
My validation code lives in a class I'm calling
SecurityServiceand looks like this:Here are some various data payloads and the result from the validation, as triggered from my unit test
Edit: actually, my signatures are not verifying. I just was no longer getting an error. I see that the public key is parsing correctly. Digging deeper, it looks like all of the set up values are at least valid-looking (the StarkBank SDK is able to parse public key, signature with values for X,Y,and R,C), but the signature verification comes back as false. I've tried using the bodyParser as suggested, and that doesn't seem to do the trick for me, either.
=== original post ===
First of all, I was able to follow this thread and get my signature validating to work. So, thank you for that!
However, I spent the last 2 days trying to get this event signing to work, and tried a number of libraries for validating these signatures, and none of them worked. I am on node 10, so the example linked in the documentation that references the Node Crypto library wouldn't work for me (as it was only available on node 14).
I tried all of the popular elliptic crypto libraries on NPM, and none of them worked for lots of reasons- unable to parse the signature, unable to parse the public key, etc. I tried all of the different curve options provided by the
ellipticlibrary on npm. All failed for different reasons.None of my googling surfaced the Starkbank library. Even if i found it, i likely wouldn't have trusted it as only been downloaded ~100 times.
I wrote to support, and they attempted to escalate the request - still haven't heard back.
It wasn't until a friend dug out the pull request from @eshanholtz for the
eventwebhookpackage that I was able to find a solution. _THANK YOU_ for that package.The good news is that using the package, is as able to verify signatures on the first try.
Please, please, please update your documentation/readme to at least _mention_ all of the various SDKs that are available in this monorepo. That small hint would have saved me 2 days of frustration.