Webhooks tell your other tools what happened in NudgeLink, the moment it happens. They only report: a webhook never sends a message to a lead or changes a campaign. They are included in every paid plan.
Set one up
- Open Settings → Integrations → Webhooks.
- Paste an
httpsaddress that accepts POST requests. In Zapier, Make or n8n this is the "catch hook" URL of a scenario. - Choose the events you want and select Add webhook.
- Copy the signing secret. It is shown once. If you lose it, delete the webhook and add it again.
- Select Send test event to check that the receiving side works.
You can have up to 3 webhooks. The address must be reachable on the public internet: private, local and cloud-metadata addresses are refused, and redirects are not followed.
Events
| Event | When it is sent |
|---|---|
reply.received | A lead replies, on LinkedIn or by email. |
reply.positive | A reply is classified as positive. This can arrive a little after reply.received. |
lead.added | A lead is added to your account (discovery, import or by hand). |
sequence.finished | A lead's sequence has run to its end. |
ping | You pressed Send test event. |
Only events that happen after you create a webhook are sent; nothing from the past is replayed. Delivery usually takes under two minutes.
Each request is a JSON POST:
{
"id": "0b0e9f6e-6a56-4c3e-9c63-0e8f0f2b7a11",
"type": "reply.positive",
"created_at": "2026-10-26T14:03:11+00:00",
"client_id": "…",
"data": {
"lead": { "id": "…", "name": "Ada Nowak", "title": "CEO", "company": "Acme", "linkedin_url": "https://www.linkedin.com/in/…" },
"campaign": "Mapa Klientów · PL B2B",
"reply": { "channel": "linkedin", "classification": "positive", "text": "Chętnie porozmawiam…" }
}
}
Reply text is cut at 2,000 characters.
Check the signature
Every request carries these headers:
NudgeLink-Signature: t=<unix time>,v1=<hex>NudgeLink-Event: the event typeNudgeLink-Event-Id: the sameidas in the body
v1 is the hex HMAC-SHA256 of the text "<t>.<raw request body>", keyed with your signing secret. Compute it on the raw body, compare in constant time, and reject requests where t is more than 5 minutes old.
import hashlib, hmac, time
def verify(secret: str, raw_body: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Retries and duplicates
Answer with any 2xx status within 10 seconds. Anything else counts as a failure and is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then given up. After 20 failed attempts in a row a webhook is switched off, and Settings shows why; turn it back on once the receiver is fixed.
A request can arrive more than once, for example after a retry. Every attempt for the same event carries the same id, so your receiver can ignore ids it has already handled.
Delivery records are kept for 30 days. When you delete your account, your webhooks and their delivery records are deleted with it.