Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

event webhook - please provide example for usage in express #1142

Closed
psteinroe opened this issue Jun 11, 2020 · 9 comments · Fixed by #1153
Closed

event webhook - please provide example for usage in express #1142

psteinroe opened this issue Jun 11, 2020 · 9 comments · Fixed by #1153
Labels
status: waiting for feedback waiting for feedback from the submitter type: question question directed at the library

Comments

@psteinroe
Copy link

psteinroe commented Jun 11, 2020

Issue Summary

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.

Steps to Reproduce

  1. Setup an express server, deploy it somewhere and trigger the Webhook.

Code Snippet

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":"heyanna-test@hotmail.com","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":"<A5cLt0y0SYyA-sv7tV5D2w@ismtpd0087p1mdw1.sendgrid.net>","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"

Exception/Log

Unauthorized Request

@childish-sambino
Copy link
Contributor

Payload in the sample is an array? Maybe try just passing in the contained object itself.

@childish-sambino childish-sambino added status: waiting for feedback waiting for feedback from the submitter type: question question directed at the library labels Jun 11, 2020
@psteinroe
Copy link
Author

Thanks for the hint, but it does not work. Your docs also state that the entire payload has to be used.

@eshanholtz
Copy link
Contributor

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.

@eshanholtz eshanholtz added type: bug bug in the library type: question question directed at the library and removed type: question question directed at the library type: bug bug in the library labels Jun 15, 2020
@eshanholtz
Copy link
Contributor

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!

@neilpoulin
Copy link

neilpoulin commented Jun 19, 2020

Edit 3 (Final update..). Got live requests working 🎉
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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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\":\"example@test.com\",\"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.

neilpoulin added a commit to Kinecho/cactus that referenced this issue Jun 19, 2020
@psteinroe
Copy link
Author

psteinroe commented Jun 20, 2020

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().

@Piccirello
Copy link

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.

@eshanholtz
Copy link
Contributor

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 !).

@eshanholtz
Copy link
Contributor

Changes will be included in the next release scheduled for tomorrow, 2020-06-24

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting for feedback waiting for feedback from the submitter type: question question directed at the library
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants