Integrations
Leadnote webhooks: events, signatures and retries
Send signed lead events from Leadnote to your own server, verify the signature, understand retries and replays, and rotate the signing secret safely.
An outbound webhook makes Leadnote send a signed JSON message to an address you choose when something happens, such as a lead being created. Use it to keep your own system in step without polling. Only Account Owners and Managers can add, change and delete webhooks.
This article is about webhooks that Leadnote sends. Portals and tools that post leads into Leadnote use a private webhook URL instead. See IndiaMART and lead portals.
Add a webhook
- Open Settings and choose API Integration, or find the Other Enterprise Systems (API) card on the integrations hub. The page is titled API and webhooks.
- Open the Outbound webhooks tab and choose Add webhook.
- Enter the Endpoint URL. It must be a public https:// (or http://) address.
- Add a Description if you like, such as "Billing system".
- Under events, choose Select events and tick the events you want. You need at least one.
- Choose Create webhook.
- Copy the signing secret from the window that opens. It is shown once. If you lose it, rotate the secret.
The list shows each endpoint with its events, its Active or Paused status, the number of deliveries and the date of the last one. In the row menu you can Edit, Send test, Rotate secret and Delete. Switch a webhook to Paused: no events are sent to stop it without deleting it.
Events
| Event | When it is sent |
|---|---|
lead.created |
A lead is added, by a person, an import, a form or an integration |
lead.updated |
A lead's details change |
lead.deleted |
A lead is deleted or merged into another |
lead.stage_changed |
A lead moves to another stage |
lead.assigned |
A lead gets a new owner |
campaign.completed |
A campaign finishes sending |
import.completed |
An import job finishes |
integration.needs_reconnect |
An integration login expired and needs reconnecting |
What Leadnote sends
Each delivery is a POST with a JSON body:
{
"id": "DELIVERY_ID",
"event": "lead.created",
"createdAt": "2026-10-29T10:00:00.000Z",
"data": { "id": "LEAD_ID", "listId": "LIST_ID", "firstName": "Asha", "email": "asha@example.com", "phone": "+919876543210", "source": "Enquiry form" }
}
The data object depends on the event. Event data holds ids and the changed values, not the whole lead. Read the full lead with the public API if you need more. See the public API for developers.
| Header | Meaning |
|---|---|
X-Leadnote-Event |
The event name |
X-Leadnote-Delivery |
The delivery id. It is the same on every retry, so use it to ignore duplicates. |
X-Leadnote-Timestamp |
Unix seconds when the delivery was signed |
X-Leadnote-Signature |
v1= followed by the hex HMAC-SHA256 signature |
X-Leadnote-Signature-Previous |
Only while a rotated secret is still in its grace period |
X-Leadnote-Replay-Of |
Only on a replay: the id of the original delivery |
Verify the signature
The signature is the HMAC-SHA256 of the text timestamp.body, made with your webhook secret. In Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
function isValid(secret, timestamp, rawBody, signature) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // older than 5 minutes
const expected = 'v1=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(signature || '');
return a.length === b.length && timingSafeEqual(a, b);
}
Follow these rules:
- Compute the signature over the raw bytes you received, never over re-serialised JSON.
- Compare in constant time.
- Reject a delivery that is more than 5 minutes old.
- Ignore a delivery id you have already processed.
Retries and dead deliveries
Answer with any 2xx status within 8 seconds to accept a delivery. Leadnote does not follow redirects. If your server fails, answers with another status or times out, Leadnote retries after 1 minute, 5 minutes, 30 minutes and 2 hours. After the fifth attempt the delivery is marked Dead.
A delivery shows one of four statuses: Pending, Retrying, Delivered or Dead.
Delivery log, Retry and Replay
Choose Deliveries on a webhook to see each delivery with its event, status, attempts, response and time. Filter by event and status. Open one to see the payload that was sent.
- Send test sends a sample
lead.createdevent through the real queue and shows the result. - Retry tries the same delivery again now. It keeps its id.
- Replay sends the same event and payload as a new delivery with a new id. Use it when your server already handled or refused the original for good.
Rotate the signing secret
- Choose Rotate secret in the row menu.
- Set the Grace period (hours), from 0 to 168. The default is 24. At 0 the old secret stops immediately.
- Copy the new secret. It is shown once and used at once.
During the grace period every delivery carries two signatures, X-Leadnote-Signature with the new secret and X-Leadnote-Signature-Previous with the old one. Accept either while you deploy the new secret.
Frequently asked questions
What does my server have to answer?
Any 2xx response within 8 seconds accepts the delivery. Anything else, or a timeout, counts as a failure and Leadnote retries.
How often does Leadnote retry?
After 1 minute, 5 minutes, 30 minutes and 2 hours. That is 5 attempts in total, and then the delivery is marked Dead.
What is the difference between Retry and Replay?
Retry attempts the same delivery again and keeps its id. Replay sends the same event as a new delivery with a new id and puts the original id in the X-Leadnote-Replay-Of header.
Can I point a webhook at localhost?
No. Webhooks can only call public internet addresses over http or https. Private, local and metadata addresses are refused.