This guide walks you through the process of creating a sample web app that receives webhooks from Nylas whenever you get a new email.
Before you begin
Make sure you have done the following
- Create a Nylas account- Nylas offers a free Sandbox account where you can test APIs and set up webhook triggers. Sign up to create a Nylas account.
- Made an endpoint accessible to the web, so that Nylas can make a request to your webhook. If you’re on localhost you should use a tunneling service, we recommend using the VS Code tunnel. Keep in mind that Ngrok may rate limit you and should be avoided if possible.
Set up webhooks in your app
First, we’ll set up the foundation for creating and receiving webhooks in your web app.
Configure Nylas SDKs and webhook endpoint
import "dotenv/config";import express from "express";import Nylas from "nylas";import crypto from "crypto";
const app = express();const port = 3000;
// Route to respond to Nylas webhook creation with challenge parameter.app.get("/webhooks/nylas", (req, res) => { // This occurs when you first set up the webhook with Nylas if (req.query.challenge) { console.log(`Received challenge code! - ${req.query.challenge}`);
// Enable the webhook by responding with the challenge parameter. return res.send(req.query.challenge); }
console.log(JSON.stringify(req.body.data));
return res.status(200).end();});
app.listen(port, () => { console.log(`Server is running on port ${port}`);});
# Import packagesfrom flask import Flask, request, render_templateimport hmacimport hashlibimport osfrom dataclasses import dataclassimport pendulum
# Array to hold webhook dataclasswebhooks = []
# Webhook dataclass@dataclassclass Webhook: _id: str date: str subject: str from_email: str from_name: str
# Get today’s datetoday = pendulum.now()
# Create the Flask app and load the configurationapp = Flask(__name__)
# Read and insert webhook data@app.route("/webhook", methods=["GET", "POST"])def webhook(): # We are connected to Nylas, let’s return the challenge parameter. if request.method == "GET" and "challenge" in request.args: print(" * Nylas connected to the webhook!") return request.args["challenge"]
if request.method == "POST": is_genuine = verify_signature( message=request.data, key=os.environ["WEBHOOK_SECRET"].encode("utf8"), signature=request.headers.get("x-nylas-signature"), )
if not is_genuine: return "Signature verification failed!", 401 data = request.get_json()
hook = Webhook( data["data"]["object"]["id"], pendulum.from_timestamp( data["data"]["object"]["date"], today.timezone.name ).strftime("%d/%m/%Y %H:%M:%S"), data["data"]["object"]["subject"], data["data"]["object"]["from"][0]["email"], data["data"]["object"]["from"][0]["name"], )
webhooks.append(hook)
return "Webhook received", 200
# Main page@app.route("/")def index(): return render_template("main.html", webhooks=webhooks)
# Signature verificationdef verify_signature(message, key, signature): digest = hmac.new(key, msg=message, digestmod=hashlib.sha256).hexdigest()
return hmac.compare_digest(digest, signature)
# Run our applicationif __name__ == "__main__": app.run()
# frozen_string_literal: true
# Load gemsrequire 'nylas'require 'sinatra'require 'sinatra/config_file'
webhook = Data.define(:id, :date, :subject, :from_email, :from_name)webhooks = []
get '/webhook' do params['challenge'].to_s if params.include? 'challenge'end
post '/webhook' do # We need to verify that the signature comes from Nylas is_genuine = verify_signature(request.body.read, ENV['WEBHOOK_SECRET'], request.env['HTTP_X_NYLAS_SIGNATURE']) unless is_genuine status 401 'Signature verification failed!' end
# Read the webhook information and store it on the data class. request.body.rewind
model = JSON.parse(request.body.read)
puts(model["data"]["object"])
hook = webhook.new(model["data"]["object"]["id"], Time.at(model["data"]["object"]["date"]).strftime("%d/%m/%Y %H:%M:%S"), model["data"]["object"]["subject"], model["data"]["object"]["from"][0]["email"], model["data"]["object"]["from"][0]["name"])
webhooks.append(hook)
status 200 'Webhook received'end
get '/' do puts webhooks erb :main, locals: { webhooks: webhooks }end
# Generate a signature with our client secret and compare it with the one from Nylas.def verify_signature(message, key, signature) digest = OpenSSL::Digest.new('sha256') digest = OpenSSL::HMAC.hexdigest(digest, key, message)
secure_compare(digest, signature)end
# Compare the keys to see if they are the samedef secure_compare(a_key, b_key) return false if a_key.empty? || b_key.empty? || a_key.bytesize != b_key.bytesize
l = a_key.unpack "C#{a_key.bytesize}" res = 0
b_key.each_byte { |byte| res |= byte ^ l.shift }
res.zero?end
//webhook_info.javaimport lombok.Data;
@Datapublic class Webhook_Info { private String id; private String date; private String subject; private String from_email; private String from_name;}
// Import Spark, Jackson and Mustache librariesimport spark.ModelAndView;import static spark.Spark.*;import spark.template.mustache.MustacheTemplateEngine;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;
// Import Java librariesimport java.net.URLEncoder;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.HashMap;import java.util.Map;
// Import external librariesimport org.apache.commons.codec.digest.HmacUtils;
public class ReadWebhooks { // Function to get Hmac public static String getHmac(String data, String key) { return new HmacUtils("HmacSHA256", key).hmacHex(data); }
public static void main(String[] args) { // Array list of Webhooks ArrayList<Webhook_Info> array = new ArrayList<Webhook_Info>();
// Default path when we load our web application get("/", (request, response) -> { // Create a model to pass information to the mustache template Map<String, Object> model = new HashMap<>(); model.put("webhooks", array);
// Call the mustache template return new ModelAndView(model, "show_webhooks.mustache"); }, new MustacheTemplateEngine());
// Validate our webhook with the Nylas server get("/webhook", (request, response) -> request.queryParams("challenge"));
// Get webhook information post("/webhook", (request, response) -> { // Create JSON object mapper ObjectMapper mapper = new ObjectMapper();
// Read the response body as a Json object JsonNode incoming_webhook = mapper.readValue(request.body(), JsonNode.class);
// Make sure we're reading our calendar if (getHmac(request.body(), URLEncoder. encode(System.getenv("WEBHOOK_SECRET"), "UTF-8")). equals(request.headers("x-nylas-signature"))) { // Create Webhook_Info record Webhook_Info new_webhook = new Webhook_Info();
// Fill webhook information System.out.println(incoming_webhook.get("data").get("object"));
new_webhook.setId(incoming_webhook.get("data"). get("object").get("id").textValue());
new_webhook.setDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"). format(new java.util.Date((incoming_webhook.get("data"). get("object").get("date").asLong() * 1000L))));
new_webhook.setSubject(incoming_webhook.get("data"). get("object").get("subject").textValue());
new_webhook.setFrom_email(incoming_webhook.get("data"). get("object").get("from").get(0).get("email").textValue());
new_webhook.setFrom_name(incoming_webhook.get("data"). get("object").get("from").get(0).get("name").textValue());
// Add webhook call to an array, so that we display it on screen array.add(new_webhook); } response.status(200); return "Webhook Received"; }); }}
import com.fasterxml.jackson.databind.JsonNodeimport com.fasterxml.jackson.module.kotlin.jacksonObjectMapperimport com.fasterxml.jackson.module.kotlin.readValueimport spark.template.mustache.MustacheTemplateEngine;
import spark.ModelAndViewimport spark.kotlin.Httpimport spark.kotlin.ignite
import java.util.*import javax.crypto.Macimport javax.crypto.spec.SecretKeySpecimport java.net.URLEncoderimport java.text.SimpleDateFormat
data class Webhook_Info( var id: String, var date: String, var subject: String, var fromEmail: String, var fromName: String)
var array: Array<Webhook_Info> = arrayOf()
object Hmac { fun digest( msg: String, key: String, alg: String = "HmacSHA256" ): String { val signingKey = SecretKeySpec(key.toByteArray(), alg) val mac = Mac.getInstance(alg)
mac.init(signingKey)
val bytes = mac.doFinal(msg.toByteArray())
return format(bytes) }
private fun format(bytes: ByteArray): String { val formatter = Formatter()
bytes.forEach { formatter.format("%02x", it) }
return formatter.toString() }}
fun addElement(arr: Array<Webhook_Info>, element: Webhook_Info): Array<Webhook_Info> { val mutableArray = arr.toMutableList() mutableArray.add(element)
return mutableArray.toTypedArray()}
fun dateFormatter(milliseconds: String): String { return SimpleDateFormat("dd/MM/yyyy HH:mm:ss"). format(Date(milliseconds.toLong() * 1000)).toString()}
fun main(args: Array<String>) { val http: Http = ignite()
http.get("/webhook") { request.queryParams("challenge") }
http.post("/webhook") { val mapper = jacksonObjectMapper() val model: JsonNode = mapper.readValue<JsonNode>(request.body()) if(Hmac.digest(request.body(), URLEncoder.encode(System.getenv("WEBHOOK_SECRET"), "UTF-8")) == request.headers("x-nylas-signature").toString()){ array = addElement(array, Webhook_Info(model["data"]["object"]["id"].textValue(), dateFormatter(model["data"]["object"]["id"].textValue()), model["data"]["object"]["subject"].textValue(), model["data"]["object"]["from"].get(0)["email"].textValue(), model["data"]["object"]["from"].get(0)["name"].textValue())) }
response.status(200) "Webhook Received" }
http.get("/") { val model = HashMap<String, Any>() model["webhooks"] = array
MustacheTemplateEngine().render( ModelAndView(model, "show_webhooks.mustache") ) }}
Prepare to log webhook notifications
import "dotenv/config";import express from "express";import Nylas from "nylas";import crypto from "crypto";
const bodyParser = require("body-parser");
const app = express();const port = 3000;
app.use(bodyParser.raw({ type: "*/*" }));
// Route to receive and validate webhook events from Nylasapp.post("/webhooks/nylas", (req, res) => { if (req.query.challenge) { return res.send(req.query.challenge); }
const nylasSignature = req.get("x-nylas-signature"); const digest = crypto .createHmac(hashAlgorithm, webhookSecret) .update(req.body) .digest("hex"); const isValidWebhook = digest === nylasSignature;
// Check to make sure it's actually Nylas that sent the webhook if (!isValidWebhook) { return res.status(401).end(); }
console.log(JSON.stringify(req.body.data));
// Responding to Nylas is important to prevent the webhook from retrying! return res.status(200).end();});
app.listen(port, () => { console.log(`Server is running on port ${port}`);});
# Inside the templates folder
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.tailwindcss.com"></script> <title>Webhooks</title></head><body> <h1 class="text-4xl font-bold dark:text-black bg-green-600 border-green-600 border-b p-4 m-4 rounded grid place-items-center">Webhooks</h1> <table style="width:100%"> <tr class="bg-green-600 border-green-600 border-b p-4 m-4 rounded"> <th>Id</th> <th>Date</th> <th>Subject</th> <th>From Email</th> <th>From Name</th> </tr> {% for webhook in webhooks: %} <tr class="bg-white-600 border-green-600 border-b p-4 m-4 rounded"> <td><p class="text-sm font-semibold">{{ webhook._id }}</p></td> <td><p class="text-sm font-semibold">{{ webhook.date }}</p></td> <td><p class="text-sm font-semibold">{{ webhook.subject }}</p></td> <td><p class="text-sm font-semibold">{{ webhook.from_email }}</p></td> <td><p class="text-sm font-semibold">{{ webhook.from_name }}</p></td> </tr> {% endfor %} </table></body></html>
# Inside the views folder
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.tailwindcss.com"></script> <title>Webhooks</title></head><body> <h1 class="text-4xl font-bold dark:text-black bg-green-600 border-green-600 border-b p-4 m-4 rounded grid place-items-center">Webhooks</h1> <table style="width:100%"> <tr class="bg-green-600 border-green-600 border-b p-4 m-4 rounded"> <th>Id</th> <th>Date</th> <th>Subject</th> <th>From Email</th> <th>From Name</th> </tr> <% webhooks.each do |item| %> <tr class="bg-white-600 border-green-600 border-b p-4 m-4 rounded"> <td><p class="text-sm font-semibold"><%= item.id %></p></td> <td><p class="text-sm font-semibold"><%= item.date %></p></td> <td><p class="text-sm font-semibold"><%= item.subject %></p></td> <td><p class="text-sm font-semibold"><%= item.from_email %></p></td> <td><p class="text-sm font-semibold"><%= item.from_name %></p></td> </tr> <% end %> </table></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.tailwindcss.com"></script> <title>Webhooks</title></head><body><h1 class="text-4xl font-bold dark:text-black bg-green-600 border-green-600 border-b p-4 m-4 rounded grid place-items-center">Webhooks</h1><table style="width:100%"> <tr class="bg-green-600 border-green-600 border-b p-4 m-4 rounded"> <th>Id</th> <th>Date</th> <th>Subject</th> <th>From Email</th> <th>From Name</th> </tr> {{#webhooks}} <tr class="bg-white-600 border-green-600 border-b p-4 m-4 rounded"> <td><p class="text-sm font-semibold">{{id}}</p></td> <td><p class="text-sm font-semibold">{{date}}</p></td> <td><p class="text-sm font-semibold">{{subject}}</p></td> <td><p class="text-sm font-semibold">{{from_email}}</p></td> <td><p class="text-sm font-semibold">{{from_name}}</p></td> </tr> {{/webhooks}}</table></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.tailwindcss.com"></script> <title>Webhooks</title></head><body><h1 class="text-4xl font-bold dark:text-black bg-green-600 border-green-600 border-b p-4 m-4 rounded grid place-items-center">Webhooks</h1><table style="width:100%"> <tr class="bg-green-600 border-green-600 border-b p-4 m-4 rounded"> <th>Id</th> <th>Date</th> <th>Subject</th> <th>From Email</th> <th>From Name</th> </tr> {{#webhooks}} <tr class="bg-white-600 border-green-600 border-b p-4 m-4 rounded"> <td><p class="text-sm font-semibold">{{id}}</p></td> <td><p class="text-sm font-semibold">{{date}}</p></td> <td><p class="text-sm font-semibold">{{subject}}</p></td> <td><p class="text-sm font-semibold">{{fromEmail}}</p></td> <td><p class="text-sm font-semibold">{{fromName}}</p></td> </tr> {{/webhooks}}</table></body></html>
Use webhooks with Nylas
Now that you have a webhook server set up, it’s time to start sending email events by setting up your webhooks on the dashboard.
You can click on “Create Webhook” and input the call back url hosted on the server you just created and select message.created
trigger. Once created, the dashboard will display your webhook secret just once, that you need to store and pass as an environment variable under WEBHOOK_SECRET
.

If all of these steps went smoothly, you’ll now get a webhook notification every time you send or receive an email!