Skip to contentSkip to navigationSkip to topbar
Rate this Page:

Validate Webhook requests from SendGrid


(warning)

Warning

This example uses headers and cookies, which are only accessible when your Function is running @twilio/runtime-handler version 1.2.0 or later. Consult the Runtime Handler guide to learn more about the latest version and how to update.

Protecting your Twilio Functions from non-Twilio requests is usually just a matter of setting a Function's visibility to Protected. However, if you'd like to create a Function that's intended to only handle incoming Webhook requests from a product such as SendGrid(link takes you to an external page), validation will require some manual inspection of headers, which are now accessible!

In this example, we'll create a Function which will serve as the Event Webhook for your SendGrid account. The Function will validate if the incoming request came from SendGrid, and send a text message to a designated phone number if an email has been opened.


Create and host a Function

create-and-host-a-function page anchor

In order to run any of the following examples, you will first need to create a Function into which you can paste the example code. You can create a Function using the Twilio Console or the Serverless Toolkit as explained below:

ConsoleServerless Toolkit

If you prefer a UI-driven approach, creating and deploying a Function can be done entirely using the Twilio Console and the following steps:

  1. Log in to the Twilio Console and navigate to the Functions tab(link takes you to an external page) . If you need an account, you can sign up for a free Twilio account here(link takes you to an external page) !
  2. Functions are contained within Services . Create a Service by clicking the Create Service(link takes you to an external page) button and providing a name such as test-function .
  3. Once you've been redirected to the new Service, click the Add + button and select Add Function from the dropdown.
  4. This will create a new Protected Function for you with the option to rename it. The name of the file will be path it is accessed from.
  5. Copy any one of the example code snippets from this page that you want to experiment with, and paste the code into your newly created Function. You can quickly switch examples by using the dropdown menu of the code rail.
  6. Click Save to save your Function's contents.
  7. Click Deploy All to build and deploy the Function. After a short delay, your Function will be accessible from: https://<service-name>-<random-characters>-<optional-domain-suffix>.twil.io/<function-path>
    For example: test-function-3548.twil.io/hello-world .

Your Function is now ready to be invoked by HTTP requests, set as the webhook of a Twilio phone number, invoked by a Twilio Studio Run Function Widget, and more!

Validate Webhook requests from SendGrid

validate-webhook-requests-from-sendgrid page anchor

_102
const { EventWebhook, EventWebhookHeader } = require('@sendgrid/eventwebhook');
_102
_102
// Helper method for validating SendGrid requests
_102
const verifyRequest = (publicKey, payload, signature, timestamp) => {
_102
// Initialize a new SendGrid EventWebhook to expose helpful request
_102
// validation methods
_102
const eventWebhook = new EventWebhook();
_102
// Convert the public key string into an ECPublicKey
_102
const ecPublicKey = eventWebhook.convertPublicKeyToECDSA(publicKey);
_102
return eventWebhook.verifySignature(
_102
ecPublicKey,
_102
payload,
_102
signature,
_102
timestamp
_102
);
_102
};
_102
_102
exports.handler = async (context, event, callback) => {
_102
// Access a pre-initialized Twilio client from context
_102
const twilioClient = context.getTwilioClient();
_102
// Access sensitive values such as the sendgrid key and phone numbers
_102
// from Environment Variables
_102
const publicKey = context.SENDGRID_WEBHOOK_PUBLIC_KEY;
_102
const twilioPhoneNumber = context.TWILIO_PHONE_NUMBER;
_102
const numberToNotify = context.NOTIFY_PHONE_NUMBER;
_102
_102
// The SendGrid EventWebhookHeader provides methods for getting
_102
// the necessary header names.
_102
// Remember to cast these header names to lowercase to access them correctly
_102
const signatureKey = EventWebhookHeader.SIGNATURE().toLowerCase();
_102
const timestampKey = EventWebhookHeader.TIMESTAMP().toLowerCase();
_102
_102
// Retrieve SendGrid's headers so they can be used to validate
_102
// the request
_102
const signature = event.request.headers[signatureKey];
_102
const timestamp = event.request.headers[timestampKey];
_102
_102
// Runtime injects the request object and spreads in the SendGrid events.
_102
// Isolate the original SendGrid event contents using destructuring
_102
// and the rest operator
_102
const { request, ...sendGridEvents } = event;
_102
// Convert the SendGrid event back into an array of events, which is the
_102
// format sent by SendGrid initially
_102
const sendGridPayload = Object.values(sendGridEvents);
_102
_102
// Stringify the event and add newlines/carriage returns since they're expected by validator
_102
const rawEvent =
_102
JSON.stringify(sendGridPayload).split('},{').join('},\r\n{') + '\r\n';
_102
_102
// Verify the request using the public key, the body of the request,
_102
// and the SendGrid headers
_102
const valid = verifyRequest(publicKey, rawEvent, signature, timestamp);
_102
// Reject invalidated requests!
_102
if (!valid) return callback("Request didn't come from SendGrid", event);
_102
_102
// Helper method to simplify repeated calls to send messages with
_102
// nicely formatted timestamps
_102
const sendSMSNotification = (recipientEmail, timestamp) => {
_102
const formattedDateTime = new Intl.DateTimeFormat('en-US', {
_102
year: 'numeric',
_102
month: 'numeric',
_102
day: 'numeric',
_102
hour: 'numeric',
_102
minute: 'numeric',
_102
second: 'numeric',
_102
hour12: true,
_102
timeZone: 'America/Los_Angeles',
_102
}).format(timestamp);
_102
_102
return twilioClient.messages.create({
_102
from: twilioPhoneNumber,
_102
to: numberToNotify,
_102
body: `Email to ${recipientEmail} was opened on ${formattedDateTime}.`,
_102
});
_102
};
_102
_102
// Convert the original list of events into a condensed version for SMS
_102
const normalizedEvents = sendGridPayload
_102
.map((rawEvent) => ({
_102
to: rawEvent.email,
_102
timestamp: rawEvent.timestamp * 1000,
_102
status: rawEvent.event,
_102
messageId: rawEvent.sg_message_id.split('.')[0],
_102
}))
_102
// Ensure that events are sorted by time to ensure they're sent
_102
// in the correct order
_102
.sort((a, b) => a.timestamp - b.timestamp);
_102
_102
// Iterate over each event and wait for a text to be sent before
_102
// processing the next event
_102
for (const event of normalizedEvents) {
_102
// You could also await an async operation to update your db records to
_102
// reflect the status change here
_102
// await db.updateEmailStatus(event.messageId, event.status, event.timestamp);
_102
if (event.status === 'open') {
_102
await sendSMSNotification(event.to, event.timestamp);
_102
}
_102
}
_102
_102
// Return a 200 OK!
_102
return callback();
_102
};


Create your Function and connect it to SendGrid

create-your-function-and-connect-it-to-sendgrid page anchor
  1. First, create a new sendgrid-email Service and add a Public /events/email Function. Delete the default contents of the Function, and paste in the code snippet provided on this page.
  2. Create a free SendGrid account(link takes you to an external page) .
  3. Follow the instructions here(link takes you to an external page) to set up a SendGrid Event Webhook. Paste the URL of your newly created Function as the unique URL for the Event Webhook. (it will look like https://sendgrid-email-5877.twil.io/events/email)

    sendgrid event webhook url.
  4. Follow these steps(link takes you to an external page) to enable the Signed Event Webhook Requests. This will add signed SendGrid headers to incoming webhook requests, which we can then use to validate requests!

    Enable SendGrid Signed Event Webhook Requests.
  5. Copy the generated Verification Key from the last step, and save it as an Environment variable in Runtime as SENDGRID_WEBHOOK_PUBLIC_KEY. While here, also save your TWILIO_PHONE_NUMBER (from the Twilio console) and a NOTIFY_PHONE_NUMBER (this could be your personal phone number for now)

    Set SendGrid verification key as an environment variable.
  6. Add the @sendgrid/eventwebhook dependency as *, and ensure that the @twilio/runtime-handler dependency is set to version 1.3.0 or later to enable headers.

    sendgrid webhook dependency.
  7. Save your Function and deploy it by clicking on Deploy All.

    sendgrid email deploy all.

Now that you've deployed your Function, it's time to validate that your code and its integration with SendGrid is working properly. In order to do so, you'll need to generate some email events. This will be accomplished with a short script written in JavaScript and using the @sendgrid/mail library.

Setup environment variables

setup-environment-variables page anchor

First, grab your SendGrid API Key(link takes you to an external page) (or create one!). For security, we'll be setting it as an environment variable and using it in our code instead of directly hard-coding it. You can do so by performing the following commands in your terminal, making sure to replace the YOUR_API_KEY placeholder with your own key.


_10
echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env
_10
echo "sendgrid.env" >> .gitignore
_10
source ./sendgrid.env

Install the helper library

install-the-helper-library page anchor

Next, use npm(link takes you to an external page) or yarn to install the Node.js SendGrid helper library which will enable you to send emails using JavaScript. If you already have Node.js(link takes you to an external page) installed, it's very likely you already have npm available and ready to use.


_10
npm install --save @sendgrid/mail

If you prefer yarn(link takes you to an external page) instead:


_10
yarn add @sendgrid/mail

Add and verify a Sender Identity

add-and-verify-a-sender-identity page anchor

Before you can successfully send an email, you'll need to verify an email address or domain in the Sender Authentication tab(link takes you to an external page). Without this, you will receive a 403 Forbidden response when attempting to send mail.

Once you have prepared your environment variables, installed the SendGrid helper library, and your email has been validated, you're ready to send some emails and create some events.

Create a new file such as send-email.js, and paste in the following snippet. Be sure to replace the from variable with your verified email address from earlier, as well as the to variable (in this case, you can use your verified email address here as well). Save the file.


_19
// Using Twilio SendGrid's v3 Node.js Library
_19
// https://github.com/sendgrid/sendgrid-nodejs
_19
const sgMail = require('@sendgrid/mail');
_19
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
_19
_19
sgMail
_19
.send({
_19
from: 'test@example.com', // Change to your verified sender
_19
to: 'test@example.com', // Change to your recipient
_19
subject: 'Sending with Twilio SendGrid is Fun',
_19
text: 'and easy to do anywhere, even with Node.js',
_19
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
_19
})
_19
.then(() => {
_19
console.log('Email sent');
_19
})
_19
.catch((error) => {
_19
console.error(error);
_19
});

Once the script is saved, you can send your test email by executing the script with Node.js:


_10
node send-email.js

Once you've received your email and opened it, you should receive a text message from your Function a short time later!

If you would like to expedite this process a bit and not wait for the open event itself, you could modify line 95 of the Function body to instead check for delivered events instead. A delivered event will be emitted and processed by your Function almost immediately after executing the send-email script.


Rate this Page: