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, orgrant.expiredevents? - 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.created | A new email is received on the grant |
message.updated | A message changes (e.g. read/unread, folder) |
message.opened | A tracked outbound message is opened |
message.link_clicked | A tracked link in a message is clicked |
message.bounce_detected | An outbound message bounces |
event.created | A calendar event is created |
event.updated | A calendar event is updated |
event.deleted | A calendar event is deleted |
grant.created | An account grant is created (account connected) |
grant.expired | A grant's credentials expire — re-auth required |
grant.deleted | A 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
- references/overview.md - Nylas webhook concepts, triggers, CloudEvents payloads
- references/setup.md - Dashboard/API configuration, challenge handshake, getting the secret
- references/verification.md - Signature verification details, gzip gotcha, debugging