Guide

Email Webhook Events: Developer Reference

You need real-time events, not polling. This reference covers every Nylas webhook trigger type across email, calendar, contacts, and grants — with payload shapes, retry behavior, HMAC verification, and the CLI commands to register and test them.

Written by Pouya Sanooei Software Engineer

VerifiedCLI 3.1.10 · last tested May 30, 2026

Polling the API every N seconds costs quota and adds latency. Webhooks let Nylas push a signed event to your endpoint within seconds of a change. The Nylas webhooks documentation covers the full platform contract. This page is the quick-reference you bookmark: trigger names, payload shapes, retry rules, and the CLI commands to wire everything up quickly.

What trigger types are available for email, calendar, and contacts?

A webhook trigger is a dot-separated string that identifies the object type and the change that occurred. Nylas groups triggers into 4 categories: message, event, contact, and grant. Each category has a created, updated, and deleted variant. Grant events are the exception — they use created, updated, and expired. As of Nylas v3, there are several trigger types across four categories.

The fastest way to get the current list is nylas webhook triggers. Pass --category to filter by object type and --json to pipe the output into a script or config file.

# List all trigger types
nylas webhook triggers --json

# Filter to message triggers only
nylas webhook triggers --category message --json

# Filter to calendar event triggers
nylas webhook triggers --category event --json

Trigger reference table

Run nylas webhook triggers for the latest list. Use the exact string when passing --triggers to nylas webhook create.

TriggerCategoryFires when
message.createdmessageA new email arrives in the mailbox
message.updatedmessageLabels, read state, or metadata change on an existing message
message.deletedmessageA message is permanently deleted or expunged
event.createdeventA new calendar event is added
event.updatedeventTitle, time, attendees, or status change on an existing event
event.deletedeventA calendar event is removed
contact.createdcontactA new contact is added to the address book
contact.updatedcontactName, email, phone, or group membership changes
contact.deletedcontactA contact is removed from the address book
grant.createdgrantA user completes OAuth and a grant is issued
grant.updatedgrantToken is refreshed or scopes change
grant.expiredgrantAccess token expires and can no longer be refreshed

What does a webhook payload look like?

Every Nylas webhook payload is a JSON object with a top-level type field that matches the trigger name, a data.object block with the changed resource, and a id field you should use as a deduplication key. The shape is consistent across all 4 categories. Your handler can branch on type without parsing the full body first.

Use nylas webhook test payload to generate a sample for any trigger. The output is deterministic and safe to commit as a test fixture. The example below shows a message.created payload — the same structure applies to all message triggers, with type changing to message.updated or message.deleted as appropriate.

{
  "specversion": "1.0",
  "type": "message.created",
  "id": "8d7f3c2a-1b4e-4a9d-9e0f-5c2b7a8d1e3f",
  "time": 1748563200,
  "webhook_delivery_attempt": 1,
  "data": {
    "application_id": "app_01abc23def456",
    "object": {
      "id": "msg_01abc23def456",
      "grant_id": "grant_01abc23def456",
      "subject": "Weekly report",
      "from": [{ "name": "Alice", "email": "alice@example.com" }],
      "to": [{ "name": "Bob", "email": "bob@example.com" }],
      "date": 1748563200,
      "thread_id": "thread_01abc23def456",
      "unread": true,
      "starred": false,
      "folders": ["INBOX"]
    }
  }
}

The webhook_delivery_attempt field increments on each retry attempt. A value greater than 1 means Nylas is retrying. Your handler should use id as the dedupe key and skip duplicate processing if the event was already handled successfully.

How do you create a webhook with multiple triggers?

The nylas webhook create command registers a public HTTPS endpoint and binds it to one or more trigger types. Pass a comma-separated list to --triggers. A single endpoint can subscribe to every available trigger type, or you can separate concerns across multiple endpoints. Registration is instant and the endpoint starts receiving events immediately.

# Install Nylas CLI
brew install nylas/nylas-cli/nylas

# Authenticate with your API key
nylas auth config --api-key YOUR_API_KEY

# Create a webhook for email and calendar events
nylas webhook create \
  --url https://api.example.com/nylas/webhooks \
  --triggers "message.created,event.updated"

# Subscribe to all 4 categories at once
nylas webhook create \
  --url https://api.example.com/nylas/webhooks \
  --triggers "message.created,message.updated,event.created,event.updated,grant.expired"

# List active webhooks to confirm registration
nylas webhook list

How does Nylas retry failed webhook deliveries?

Nylas retries a non-200 (or timed-out) delivery two more times — three attempts in total — backing off exponentially, with the final attempt landing 10–20 minutes after the first. According to the Nylas webhooks documentation, your endpoint must return a 200 OK response within 10 seconds or Nylas counts the attempt as failed. Separately, if a destination misses about 95% of notifications over a 72-hour window, Nylas marks the endpoint as failed and stops sending to it.

Two rules follow from this. First, your handler should acknowledge immediately and process asynchronously — enqueue the event, return 200, and do the work in a background job. Second, your handler must be idempotent because Nylas can deliver the same event more than once. The id field in the payload is stable across retries, making it the right dedupe key.

Retry behavior at a glance

PropertyValue
Success condition200 OK within 10 seconds
Retry attempts3 total (initial + 2 retries)
Backoff strategyExponential; final attempt 10–20 min after the first
Dedupe keyid field in payload root
Attempt counterwebhook_delivery_attempt in payload
After 3 failed attemptsNotification is skipped; ~95% loss over 72h disables the endpoint

How does HMAC signature verification work?

Nylas signs every webhook request with HMAC-SHA256 using your webhook secret. The signature appears in the X-Nylas-Signature request header. To verify it, compute HMAC-SHA256 of the raw request body bytes using the same secret, then compare the result to the header value with a constant-time comparison. If the signature doesn't match, reject the request before parsing JSON or touching any database.

The raw-body requirement is strict: JSON whitespace and field order affect the byte sequence, so re-serializing a parsed object before verifying will fail. The CLI nylas webhook verify command handles this correctly. Use it locally during development to confirm your secret and payload pair before wiring the same logic into your app.

# Save raw payload to a file (before JSON parsing)
# Then verify the signature from the X-Nylas-Signature header
export NYLAS_WEBHOOK_SECRET="your_webhook_secret"
export NYLAS_SIGNATURE="header_value_from_request"

nylas webhook verify \
  --payload-file payload.raw.json \
  --signature "$NYLAS_SIGNATURE" \
  --secret "$NYLAS_WEBHOOK_SECRET"

A Node.js app should use express.raw() to capture the buffer before any middleware touches it. The same applies in Python: read request.get_data() before calling request.get_json(). Catching a signature mismatch before the payload is parsed catches forged requests, replayed events with stale secrets, and middleware that normalizes whitespace. The HMAC-SHA256 verification is computationally trivial, so there's no performance reason to skip it.

How do you test webhooks during local development?

Testing webhooks locally requires 3 things: a public tunnel to your localhost, a registered endpoint pointing at that tunnel, and a way to replay signed payloads without waiting for real provider events. The CLI provides all 3 in a single command group. Starting the local receiver takes seconds and works on any port.

The nylas webhook server command starts a local HTTP listener and optionally spins up a Cloudflare tunnel to expose it publicly. Once the tunnel URL is ready, register it with nylas webhook create and use nylas webhook test send to deliver a test event to the endpoint.

# Start a local receiver on port 3000 with a Cloudflare tunnel
export NYLAS_WEBHOOK_SECRET="local_dev_secret"
nylas webhook server \
  --port 3000 \
  --secret "$NYLAS_WEBHOOK_SECRET" \
  --tunnel cloudflared

# In a second terminal: register the tunnel URL as a webhook endpoint
nylas webhook create \
  --url https://your-tunnel-subdomain.trycloudflare.com/webhook \
  --triggers "message.created,event.updated"

# Send a test event to the registered endpoint
nylas webhook test send https://your-tunnel-subdomain.trycloudflare.com/webhook

# Generate a static fixture for unit tests
nylas webhook test payload message.created --json > fixtures/message.created.json

For a complete walkthrough of the local development loop, including CI fixture patterns and idempotency tests, see the local webhook testing guide.

What are the essential webhook handler rules?

Four rules keep webhook handlers reliable in production: respond fast, verify first, deduplicate with the event ID, and process asynchronously. Skipping any one of them causes problems that only appear under load or during retry storms. Nylas's official webhook guide requires responding within 10 seconds — slow handlers are the most common cause of missed deliveries.

  • Respond in <10 seconds. Return 200 OK before doing any meaningful work. Enqueue the verified event and return immediately. Slow handlers trigger exponential backoff retries that can flood your endpoint.
  • Verify the signature before parsing. Reject any request with an invalid or missing X-Nylas-Signature header before touching the body. Never parse JSON from an unverified source.
  • Deduplicate on event ID. Store the id field after processing. If the same ID arrives again, skip the side effects. This prevents duplicate tickets, duplicate notifications, and duplicate agent tasks during retry windows.
  • Handle grant.expired explicitly. This trigger means you can no longer sync the account. Your app should notify the user to re-authenticate rather than continuing to call the API and collecting 401 errors.

How do you list, update, and delete webhooks?

After creating a webhook, you'll need to manage it over time — updating the URL when you change infrastructure, adding new trigger types as features grow, or deleting test endpoints. Each Nylas application can register multiple webhook endpoints, each with its own URL and trigger set. The CLI provides dedicated subcommands for each operation. Running nylas webhook list first gives you the IDs and current state for every registered endpoint.

# List all webhooks with full IDs
nylas webhook list --full-ids --json

# Update a webhook — add a new trigger type
nylas webhook update --id wh_abc123 \
  --triggers "message.created,message.updated,event.updated"

# Delete a test webhook
nylas webhook delete --id wh_abc123 --yes

Next steps