Guide to Nango Webhooks: Features and Best Practices
Nango webhooks tell your app what is happening to the integrations your users connect: a new connection is created, an OAuth token fails to refresh, a sync run finishes, or an external API sends a webhook that Nango forwards on. If you use Nango to manage auth and data syncs for third-party APIs, these webhooks are how your app learns about new connections and when to fetch fresh records.
This guide covers how Nango webhooks are configured and signed, the webhook types and their payloads, how retries and timeouts work, and the best practices for running a Nango receiver in production.
What are Nango webhooks?
Nango sends webhooks from each environment to the URLs you configure in that environment's settings. "All webhooks from Nango are POST requests", with a JSON body.
There are no fine-grained event names. The top-level type field says which kind of webhook it is: auth, sync, forward or async_action. Auth webhooks are refined by operation, and auth and sync webhooks both carry a success flag.
Each request is signed with an X-Nango-Hmac-Sha256 header, a hex HMAC-SHA256 of the raw body keyed with the environment's webhook signing key. There is no timestamp, and no event or delivery ID.
Nango webhook features
| Feature | Details |
|---|---|
| Configuration | Environment Settings > Webhook URLs, per environment |
| Destinations | Up to two URLs per environment. Both receive every webhook |
| Per-connection URL | webhook_url_override replaces both environment URLs for one connection |
| Method and format | POST with a JSON body |
| Webhook type | Top-level type: auth, sync, forward or async_action |
| Signature header | X-Nango-Hmac-Sha256 |
| Scheme | HMAC-SHA256 over the raw body, hex encoded |
| Secret | The environment's webhook signing key, not the API key |
| Legacy header | X-Nango-Signature (plain SHA-256). Don't use it |
| Key rotation | Either key may sign for up to 5 minutes after a rotation |
| Retries | 2 retries on a non-2xx, exponential backoff from 100 ms |
| Timeout | 20 seconds |
| Logs | Every attempt is logged in Nango's logs |
| Event ID | None |
Nango publishes no source IP list for webhook traffic.
Common webhook types
Auth webhooks
Auth webhooks report connection lifecycle changes. The operation field is one of:
creation: a new connection was createdoverride: a connection was re-authorisedrefresh: an OAuth token failed to refresh, or recovered after a previous failuredeletion: a connection was deleted through the dashboard or the API
Nango calls processing auth creation webhooks "necessary", because they give you the connection ID you need to use the connection later. A creation webhook looks like this:
{
"type": "auth",
"operation": "creation",
"connectionId": "<your-connection-id>",
"authMode": "OAUTH2",
"providerConfigKey": "<your-integration-id>",
"provider": "<your-provider-key>",
"environment": "PROD",
"success": true,
"tags": {
"end_user_id": "<your-end-user-id>",
"end_user_email": "<your-end-user-email>",
"organization_id": "<your-organization-id>"
}
}
Use tags to link the connection ID with the user or organisation that created it. A failed refresh has "success": false and an error object with type and description. When a later refresh succeeds, Nango sends another refresh webhook with "success": true.
Sync webhooks
A sync webhook is sent when a sync execution finishes, whether it succeeded or not. A successful one carries syncName, model, modifiedAfter, responseResults (counts of added, updated and deleted records) and checkpoints. A failed one carries error, startedAt and failedAt instead of the results.
To tell a full sync from an incremental one, use checkpoints, not the deprecated syncType. If checkpoints is missing or null, or checkpoints.from is null, treat the run as a full sync.
Forwarded webhooks
With webhook forwarding, an external API sends its webhooks to Nango and Nango passes them on to your URL, with the same signing and retries. When Nango can match the webhook to a connection, you get a wrapper:
{
"from": "hubspot",
"providerConfigKey": "<INTEGRATION-ID>",
"type": "forward",
"connectionId": "<CONNECTION-ID>",
"payload": {
"...": "raw provider payload"
}
}
If Nango can't match it to a connection, it forwards the raw provider payload with no wrapper and no type. If Nango skipped the provider's own signature check, it sets the header X-Nango-Webhook-Unverified: true and adds "unverified": true to the wrapper.
Async action webhooks
async_action is the fourth type. Nango's webhooks page does not print an example of it.
Setting up Nango webhooks
- Create a
POSTendpoint in your app. - In the Nango dashboard, open Environment Settings for the environment and enter your URL under Webhook URLs. You can add a second URL. Both receive every webhook.
- In the same settings, choose which webhooks the environment sends. For example, sync webhooks for runs with no changes are sent by default and can be turned off.
- Copy the signing key from Environment Settings > Webhooks > Signing key and store it, for example as
NANGO_.WEBHOOK_ SIGNING_ KEY
To send one connection's webhooks somewhere else, set webhook_url_override when you create its connect session, on the connection's Settings tab, or with PATCH /connections/{connectionId}. The override replaces both environment URLs for that connection. It doesn't add a third destination.
Securing Nango webhooks
Compute an HMAC-SHA256 of the raw body with your signing key, hex-encode it, and compare it in constant time with X-Nango-Hmac-Sha256. Nango warns that the signing key and the environment API key are different keys, and using the API key "will cause verification to fail on any environment where the keys differ".
const crypto = require('crypto');
const express = require('express');
function verifyNangoSignature(rawBody, signatureHeader, signingKey) {
if (typeof signatureHeader !== 'string' || !signingKey) return false;
const expected = crypto.createHmac('sha256', signingKey).update(rawBody).digest();
const received = Buffer.from(signatureHeader.trim().toLowerCase(), 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/nango', express.raw({ type: '*/*' }), (req, res) => {
const signingKey = process.env.NANGO_WEBHOOK_SIGNING_KEY;
if (!signingKey) return res.sendStatus(500); // fail closed
if (!verifyNangoSignature(req.body, req.headers['x-nango-hmac-sha256'], signingKey)) {
return res.sendStatus(401);
}
const webhook = JSON.parse(req.body.toString('utf8'));
// Route on webhook.type, ignore types you don't handle, then process async.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
def verify_nango_signature(raw_body: bytes, signature_header: str, signing_key: str) -> bool:
if not signature_header or not signing_key:
return False
expected = hmac.new(signing_key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), signature_header.strip().lower().encode())
Nango's Node SDK does the same check with nango.verifyIncomingWebhookRequest(req.body, req.headers). Pass the signing key as webhookSigningKey when you create the client, because it falls back to secretKey when omitted.
To check your verifier, use the signing key 3f8e2a1c-7b4d-4e9f-a6c5-1d2b3e4f5a6b and this exact body:
{"type":"auth","operation":"creation","connectionId":"user-123","success":true}
The X-Nango-Hmac-Sha256 header should be:
1230448ade97bc7f171c0ffc88db5d58b6ffcc9902f9804631b08d0adae5279e
Make Nango webhooks production-ready. Hookdeck Event Gateway verifies
X-Nango-Hmac-Sha256on every delivery, queues auth and sync webhooks durably, and retries far beyond Nango's two attempts.
Nango webhook limitations and pain points
Only two retries
The Problem: A short outage or a slow deploy on your side means lost auth creation webhooks, and with them the connection IDs your app needs.
Why It Happens: Nango retries a non-2xx response 2 times with exponential backoff starting at 100 ms, so the waits between attempts add up to well under a second. Each attempt times out after 20 seconds.
Workarounds:
- Return a
2xxas soon as the signature checks out, and do the work afterwards. - Check Nango's logs for failed attempts after an outage, and act on any you missed.
How Hookdeck Can Help: Hookdeck accepts each webhook as soon as it arrives, so Nango's attempt succeeds even while your service is down. You then set your own retry policy and replay anything that failed.
No event ID
The Problem: A retried webhook looks like a new one. So does the second copy, if both environment URLs point at the same service.
Why It Happens: Nango's payloads have no event ID and Nango documents no delivery ID header. Retries resend the same body.
Workarounds:
- Make writes idempotent. For
authwebhooks, upsert onconnectionIdandoperation. Forsyncwebhooks, treat the webhook as a signal to fetch changed records, which is safe to repeat. - If you need a key, build one from fields such as
type,connectionId,syncNameandmodifiedAfter.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the fields you choose, such as type, connectionId and modifiedAfter.
Signing key rotation
The Problem: After you rotate the signing key, some webhooks fail verification for a few minutes.
Why It Happens: For up to 5 minutes after a rotation, Nango signs with either the old or the new key. A receiver that knows only one key rejects the others.
Workarounds:
- Accept both keys for those 5 minutes, then drop the old one.
- Rotate during a quiet period.
How Hookdeck Can Help: A Hookdeck source holds one signing key at a time. Update it as soon as you rotate in Nango, and expect some webhooks signed with the other key to fail verification during the overlap. Nango's logs show which ones.
Payloads have different shapes
The Problem: A handler written for auth and sync breaks on a forwarded webhook, on a raw unattributed payload, or on a type Nango adds later.
Why It Happens: Each type has its own flat body. Forwarded webhooks without a matching connection have no type at all. And Nango says "new Nango webhook types are added regularly, without considering this a breaking change".
Workarounds:
- Switch on
typeand ignore anything you don't recognise, returning a2xx. - Treat a body with no
typeas an unattributed forward. - Check
X-Nango-Webhook-Unverifiedbefore you trust a forwarded payload.
How Hookdeck Can Help: Hookdeck filters can route auth, sync and forward webhooks to different destinations, and drop forwards marked as unverified.
Best practices
Store connection IDs from auth webhooks
The connectionId in an auth creation webhook is how you reach the connection later. Save it against the user or organisation in tags as soon as it arrives.
Use the signing key, not the API key
Verify with the key from Environment Settings > Webhooks > Signing key, and keep separate keys per environment. Use X-Nango-Hmac-Sha256, not the legacy X-Nango-Signature.
Fetch records promptly after sync webhooks
Sync webhooks tell you that data changed. They don't carry the records. Fetch them soon, because Nango prunes the payload of records not updated for 30 days and deletes all records for syncs not run for 60 days.
Handle failed refreshes calmly
A refresh webhook with "success": false doesn't always mean the user must re-authorise. Nango retries refreshes on its own cycle and sends a "success": true webhook if the connection recovers.
Acknowledge fast and process asynchronously
With a 20-second timeout and only two retries, return 200 quickly and queue the work. See why to process webhooks asynchronously.
Make handlers idempotent
Processing the same webhook twice should have the same result as processing it once. See our guide to webhook idempotency.
Conclusion
Nango sends auth, sync, forward and async_action webhooks as signed JSON POSTs, with a hex HMAC-SHA256 of the raw body in X-Nango-Hmac-Sha256. Verify with the environment's signing key, switch on type and ignore what you don't know, save every new connectionId, and plan for the short retry window by acknowledging quickly.
Hookdeck Event Gateway verifies each Nango signature, holds every webhook until your service has processed it, and replays anything that failed, so two quick retries are no longer your only safety net.
Get started with Hookdeck for free and handle Nango webhooks reliably in minutes.