Create and secure webhooks with the Nylas API

Create and secure webhooks with the Nylas API

A webhook is how your app finds out about new email or calendar changes without asking. Instead of polling for "anything new yet?", you register a URL and Nylas pushes events to it as they happen. But setting one up correctly is more than posting a URL: your endpoint has to answer a challenge before the webhook activates, you get a signing secret exactly once, and you should verify a signature on every delivery so you don't process spoofed events. Get those three wrong and the webhook never turns on or your handler trusts forged data. This post sets up a webhook properly with the API and the CLI. It's a worked use case rather than an endpoint tour, covering webhooks from two angles: the HTTP API your backend calls and the nylas CLI for creating, testing, and receiving them. I work on the CLI, so the commands below are the ones I reach for when wiring up notifications. Create a webhook You register a webhook with a POST /v3/webhooks request, and the two required fields are webhook_url, where Nylas sends notifications, and trigger_types, the array of events you want delivered. An optional description labels it and notification_email_addresses gets alerts about the webhook's health, like when it's failing. curl --request POST \ --url "https://api.us.nylas.com/v3/webhooks" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --data '{ "webhook_url": "https://yourapp.com/webhooks/nylas", "trigger_types": ["message.created", "event.created"], "description": "Production message and event hook" }' Enter fullscreen mode Exit fullscreen mode Webhook management needs your API key, not a grant, because a webhook belongs to the application rather than one connected account. The trigger_types array is where you choose exactly which events to receive, from message.created to event.updated to grant.expired, so a single webhook can carry several event types or you can split them across webhooks by concern. Choose only the triggers you'll act on, since every subscribed event is a request to your endpoint. Create and explore from the CLI The CLI creates the same webhook with nylas webhook create, taking --url and --triggers as the required inputs, plus --description and --notify for the optional fields. Before you create one, nylas webhook triggers lists every available trigger type, which saves guessing at the exact event names. # See the available triggers, then create a webhook nylas webhook triggers nylas webhook create --url https://yourapp.com/webhooks/nylas \ --triggers message.created,event.created \ --notify ops@example.com Enter fullscreen mode Exit fullscreen mode Webhook management is admin-level, so the CLI uses your API key for these commands, matching the API. From there, nylas webhook list shows your application's webhooks, nylas webhook show prints one, nylas webhook update changes its triggers or URL, and nylas webhook delete removes it. The full lifecycle is on the command line, which makes the CLI handy for managing webhooks across environments without writing a single request. Answer the challenge before it activates Here's the step that trips up almost everyone the first time: before a webhook goes live, Nylas verifies your endpoint with a challenge. When you create a webhook or set one to active, it sends a GET request to your URL with a challenge query parameter, and your endpoint has to echo that exact value back in the body of a 200 OK. Until it does, the webhook doesn't activate. app.get("/webhooks/nylas", (req, res) => { res.status(200).send(req.query.challenge); // echo the exact value }); Enter fullscreen mode Exit fullscreen mode Two details make or break this. Your endpoint must respond within 10 seconds, and it must return the exact challenge value and nothing else, no JSON wrapper, no quotation marks, just the raw string. A handler that returns {"challenge": "..."} fails verification because that isn't the exact value. So a webhook endpoint needs both a GET route that answers the challenge and a POST route that receives the actual notifications, and forgetting the GET route is the most common reason a brand-new webhook silently never delivers. The webhook secret you get exactly once When your endpoint passes the challenge, Nylas generates a webhook_secret and returns it in the create response, and this is the one moment you'll see it. The secret appears in the POST /v3/webhooks response and again only if you rotate it; it is not included when you later GET the webhook. So you store it the instant you create the webhook, because there's no endpoint that hands it back later. That secret is the signing key for everything that follows, so treat it like any other credential: keep it out of source control, out of logs, and in your secrets manager. The reason it matters is the next section, every notification is signed with this secret, and without it stored you can't verify that an incoming request actually came from Nylas. Losing it isn't fatal, you can rotate to a new one, but it does mean re-storing the replacement everywhere that verifies signatures. Verify the signature on every notification This is the security step you don't skip. Every notification carries an X-Nylas-Signature header (or lowercase x-nylas-signature, depending on your stack), which is a hex-encoded HMAC-SHA256 of the exact request body, signed with your webhook_secret. You recompute that HMAC over the raw body with your stored secret and compare, and if it doesn't match, you reject the request as unverified rather than processing it. const crypto = require("crypto"); function verify(rawBody, signature, secret) { if (!/^[0-9a-f]{64}$/i.test(signature || "")) return false; // reject a missing or malformed signature const expected = crypto.createHmac("sha256", secret).update(rawBody).digest(); return crypto.timingSafeEqual(expected, Buffer.from(signature, "hex")); } Enter fullscreen mode Exit fullscreen mode The phrase "exact request body" is load-bearing. The signature is computed over the raw bytes Nylas sent, so you have to verify against the unparsed body before any middleware reformats it, since re-serializing the JSON changes the bytes and breaks the comparison. Use a constant-time comparison, as above, rather than ===, so the check doesn't leak timing information. Verifying every notification is what stops an attacker who finds your webhook URL from posting forged events that your handler would otherwise trust. The compression gotcha One detail catches people who enable payload compression. Webhooks can gzip their payloads, and when compression is on, the HMAC-SHA256 signature is computed over the compressed bytes, not the decompressed JSON. So you validate the signature against the raw compressed body first, and only decompress after the signature checks out. Reverse that order, decompress and then verify, and the signature will never match, because you're hashing different bytes than Nylas signed. This is a subtle one because it only bites when compression is enabled, so a webhook that verified fine in development can start failing signatures the moment someone turns on gzip. The rule is simple once you know it: signature first, on the bytes as received, decompression second. Rotate the secret when you need to Secrets sometimes need replacing, after a suspected leak, on a schedule, or when an employee with access leaves, and webhooks support rotation without recreating the endpoint. A POST /v3/webhooks/rotate-secret/{id} request issues a new webhook_secret and returns it, the second and only other time you see a secret value, after which notifications are signed with the new one. nylas webhook rotate-secret --yes Enter fullscreen mode Exit fullscreen mode The CLI does the same with nylas webhook rotate-secret , which requires the --yes flag to confirm the rotation, since it changes the signing key, and then prints the new secret for you to store. Without --yes, the command stops and tells you to re-run with it, a guard against rotating a production secret by accident. The operational care is to update your verification code with the new secret promptly, because once rotated, signatures use the new key immediately, and any handler still checking against the old secret will start rejecting genuine notifications. Rotate during a window where you can deploy the new secret quickly, and treat the printed value with the same care as the original. Develop locally without deploying Testing webhooks usually means deploying somewhere public, but the CLI removes that friction with a local receiver. nylas webhook server starts a local HTTP server that receives and prints webhook events, and with --tunnel cloudflared it exposes itself through a cloudflared tunnel so Nylas can reach your laptop directly, no deploy required. When the tunnel is on, you pass --secret so the server verifies the HMAC signature on each event, or --allow-unsigned to explicitly opt out. # Receive real webhooks on your laptop, verifying signatures nylas webhook server --tunnel cloudflared --secret Enter fullscreen mode Exit fullscreen mode This is the fastest way to see real notifications during development, and it handles the challenge handshake and signature verification for you, the exact two things that are fiddly to get right by hand. There's also nylas webhook test send to fire a test event at a webhook URL and nylas webhook verify to check a signature against a payload locally, so you can confirm your verification logic against a known-good example before trusting it in production. Where webhooks fit The same create-verify-receive flow sits under every event-driven feature, and the triggers you choose shape what you build. A few that map straight on: Real-time inbox sync. Subscribe to message.created, message.updated, and message.deleted to keep a local copy current the moment mail changes. Calendar automation. React to event.created and event.updated to schedule reminders or sync bookings without polling the calendar. Grant health monitoring. Listen for grant.expired so you can prompt a user to re-authenticate before their integration silently stops working. Tracking and engagement. Receive message.opened and thread.replied to drive analytics or follow-up sequences off real recipient activity. Each is the same webhook, verified the same way, with the difference being which triggers you subscribe to and what your handler does with the verified event. Things to keep in mind A short list of details keeps a webhook reliable and secure. Answer the challenge in 10 seconds. Your GET route must echo the exact challenge value, no JSON, no quotes, or the webhook never activates. Store the secret at creation. The webhook_secret is returned only on create and on rotate, never on a later GET, so save it immediately. Verify every signature. Recompute the HMAC-SHA256 over the raw body with your secret and compare in constant time; reject anything that doesn't match. Verify before decompressing. With gzip on, the signature covers the compressed bytes, so check it before you decompress. Rotate when needed. Use the rotate-secret endpoint after a leak or on a schedule, and deploy the new secret promptly so genuine events keep verifying. Develop with nylas webhook server --tunnel cloudflared. It receives real events locally and handles the challenge and signature, no deploy required. Wrapping up A secure webhook is three things done right. Create it with POST /v3/webhooks (or nylas webhook create) naming your URL and triggers, answer the challenge GET by echoing the exact value within 10 seconds so it activates, and store the webhook_secret from the create response because you only see it once. Then verify the X-Nylas-Signature HMAC on every notification against the raw body, decompressing only after the signature checks out, and rotate the secret when you need to. For development, nylas webhook server --tunnel cloudflared gives you real notifications on your laptop with the handshake and verification handled. Where to go next: Webhook notifications — the full guide to triggers, the challenge, and signatures Receive webhooks with the CLI — the local server and verification Create a webhook — the endpoint reference Nylas CLI webhook commands — nylas webhook create, rotate-secret, and server AI-answer pages for agents When this post is published, link AI agents and crawlers to the retrieval-ready version on cli.nylas.com: Topic runbook: https://cli.nylas.com/ai-answers/webhook-signature-verification-agent.md Industry playbooks hub: https://cli.nylas.com/ai-answers/agent-account-industry-playbooks.md

📰 Original Source

Read full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.