Skip to contentSkip to navigationSkip to topbar
Rate this page:
On 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.Rate this page:

    Need some help?

    Terms of service

    Copyright © 2024 Twilio Inc.