Agent skill
Nango Webhooks Skill
Receive and verify Nango webhooks. Use when setting up Nango webhook handlers, debugging X-Nango-Hmac-Sha256 signature verification (HMAC-SHA256 hex of the raw body, keyed with the environment's webhook signing key, not the API key), or handling Nango webhook types: auth (connection creation, override, refresh, deletion), sync (sync run succeeded or failed), forward (external API webhooks forwarded through Nango), and async_action.
Install this skill
npx skills add hookdeck/webhook-skills --skill nango-webhooks
When to Use This Skill
- How do I receive Nango webhooks in my app?
- How do I verify the
X-Nango-Hmac-Sha256webhook signature? - Why does Nango webhook signature verification fail? (wrong key, parsed body, legacy header)
- How do I save the connection ID from an
auth/creationwebhook? - How do I react to a
syncwebhook and fetch the changed records? - How do I handle
forwardwebhooks (HubSpot, Slack, GitHub, etc. events forwarded by Nango)?
Verification (core)
Nango signs the raw request body with HMAC-SHA256, keyed with the environment's webhook signing key (Environment Settings > Webhooks > Signing key) used as a plain UTF-8 string, and sends the lowercase hex digest in X-Nango-Hmac-Sha256. There is no timestamp or prefix. Ignore the legacy X-Nango-Signature header (plain SHA-256, deprecated).
Node (the @nangohq/node SDK does the same thing):
const { Nango } = require('@nangohq/node');
const nango = new Nango({
apiKey: process.env.NANGO_API_KEY, // API calls
webhookSigningKey: process.env.NANGO_WEBHOOK_SIGNING_KEY, // webhook verification
});
// rawBody must be the unparsed body as a string
const isValid = nango.verifyIncomingWebhookRequest(rawBody, req.headers);
Python (no SDK, manual):
import hmac, hashlib
def verify(raw_body: bytes, signature: str, signing_key: str) -> bool:
expected = hmac.new(signing_key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, (signature or "").lower())
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Webhook Types
Nango has no per-event names; route on the body's type (and operation / success for auth, success for sync). Ignore unknown types: Nango adds new ones without treating it as a breaking change.
type | Key fields | When it fires |
|---|---|---|
auth | operation: creation | override | refresh | deletion; success; connectionId; providerConfigKey; tags | Connection created, re-authorized, token refresh failed or recovered, connection deleted |
sync | success; syncName; model; modifiedAfter; responseResults; checkpoints; error | A sync function run finished (succeeded or failed) |
forward | from; providerConfigKey; connectionId (optional); payload; unverified (optional) | An external API webhook was forwarded through Nango |
async_action | connectionId; providerConfigKey; payload | An async action finished |
auth + creation with success: true is the one you must handle: it is how you learn the new connectionId (match it to your user via tags).
For payload details, see references/overview.md and Nango's webhook docs.
Important Headers
| Header | Description |
|---|---|
X-Nango-Hmac-Sha256 | Hex HMAC-SHA256 of the raw body (use this) |
X-Nango-Signature | Legacy plain SHA-256 signature (do not use) |
X-Nango-Webhook-Unverified | true on forwards where Nango could not check the external provider's signature |
Environment Variables
NANGO_WEBHOOK_SIGNING_KEY=your_webhook_signing_key # Environment Settings > Webhooks > Signing key
NANGO_API_KEY=your_nango_api_key # Environment Settings > API key (SDK constructor + API calls)
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 nango --path /webhooks/nango
Reference Materials
- references/overview.md - Webhook types and payloads
- references/setup.md - Webhook URLs, signing key, per-connection overrides
- references/verification.md - Signature verification details, key rotation, gotchas