Webhook Quickstart: create and read webhooks
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?endimport 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 isValidWebhook (req) { const signature = req.headers['x-nylas-signature']; const digest = crypto .createHmac( 'sha256', process.env.WEBHOOK_SECRET // Comes from Nylas Dashboard. Make sure to set in your env vars ) .update(req.body.data) .digest('hex')
return digest === nylasSignature}
const app = express();const port = 3000;
// Route to receive webhook events after creating webhookapp.post("/webhooks/nylas", (req, res) => { if (req.query.challenge) { return res.send(req.query.challenge); }
// Check to make sure it's actually Nylas that sent the webhook if (!isValidWebhook(req)) { 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}`);});<Include file="v3-quickstart/webhook_python_view.html" /><Include file="v3-quickstart/webhook_ruby_view.html" /><Include file="v3-quickstart/webhook_java_view.html" /><Include file="v3-quickstart/webhook_kotlin_view.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!
Next steps
Congrats! 🎉 In this Quickstart guide, you set up a simple web app, created a webhook, and received webhook notifications using Nylas.
If you’d like to see the complete code for this guide, you can find it on GitHub for the Nylas SDK language of your choice—Node.js, Python, Ruby, Java, or Kotlin.
The links below are further reading as you continue your journey to Nylas greatness: