Agent skill

Nylas Webhooks Skill

Receive and verify Nylas webhooks. Use when setting up Nylas webhook handlers, debugging x-nylas-signature verification, completing the challenge handshake, or handling email and calendar events like message.created, message.opened, event.created, event.updated, or grant.expired.

Install this skill

npx skills add hookdeck/webhook-skills --skill nylas-webhooks


When to Use This Skill

  • How do I receive Nylas webhooks?
  • How do I verify Nylas webhook signatures (x-nylas-signature)?
  • How do I respond to the Nylas challenge handshake when creating a webhook?
  • How do I handle message.created, message.opened, event.created, or grant.expired events?
  • Why is my Nylas webhook signature verification failing (gzip / raw body)?

Verification (core)

Nylas signs the raw request body with HMAC-SHA256 keyed on your per-destination webhook_secret and sends the digest as a hex string in the x-nylas-signature header (casing varies — read it case-insensitively). This is not Standard Webhooks: there is no webhook-id/webhook-timestamp; only the body is signed. Verify the raw bytes before JSON parsing, and if Content-Encoding: gzip, verify against the compressed bytes and decompress only after the check passes. Nylas SDKs expose webhook CRUD, rotateSecret, and ipAddresses, but no signature-verify helper — implement the HMAC check yourself with a constant-time comparison.

Node:

const crypto = require('crypto');

function verifyNylasSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signatureHeader, 'hex'),
      Buffer.from(expected, 'hex')
    );
  } catch {
    return false; // length mismatch = invalid
  }
}

Python:

import hmac, hashlib

def verify_nylas_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header, expected)

Challenge handshake (endpoint verification)

When a webhook destination is created (Dashboard or POST /v3/webhooks), Nylas sends a GET with a challenge query parameter. Echo the exact value back — plain text, nothing else — with 200 within 10 seconds (no chunked encoding). The webhook_secret is returned only on creation/rotation, so store it then.

// GET /webhooks/nylas?challenge=abc123  ->  200 "abc123"
app.get('/webhooks/nylas', (req, res) => res.status(200).send(req.query.challenge));

For complete handlers with the challenge route, gzip handling, event dispatch, and tests, see:

Common Event Types

Nylas payloads follow CloudEvents 1.0: the trigger is in type, and the changed resource is in data.object.

Trigger (type)Fires When
message.createdA new email is received on the grant
message.updatedA message changes (e.g. read/unread, folder)
message.openedA tracked outbound message is opened
message.link_clickedA tracked link in a message is clicked
message.bounce_detectedAn outbound message bounces
event.createdA calendar event is created
event.updatedA calendar event is updated
event.deletedA calendar event is deleted
grant.createdAn account grant is created (account connected)
grant.expiredA grant's credentials expire — re-auth required
grant.deletedA grant is deleted (account disconnected)

For the full trigger reference and payload schemas, see Nylas notification schemas.

Payload Structure (CloudEvents 1.0)

{
  "specversion": "1.0",
  "type": "message.created",
  "source": "/google/emails/realtime",
  "id": "abc-123",
  "time": 1700000000,
  "webhook_delivery_attempt": 1,
  "data": {
    "application_id": "app-uuid",
    "grant_id": "grant-uuid",
    "object": { "id": "message-id", "subject": "Hello" }
  }
}

Environment Variables

# Per-destination secret, returned when the webhook is created or its secret is rotated.
NYLAS_WEBHOOK_SECRET=your_webhook_secret

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 nylas --path /webhooks/nylas

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 3, 2026

View on GitHub →