Lead Webhooks
Send every new and updated lead from JackAI Nexus straight to your own CRM — Bitrix24, Zoho, HubSpot, n8n, Make, Zapier or your own server — with signed, retried, verifiable delivery.
Lead Webhooks
Push every new and updated lead from JackAI Nexus straight into your own CRM — Bitrix24, Zoho, HubSpot, Salesforce, an automation tool like n8n, Make or Zapier, or your own server.
Instead of asking your team to copy leads by hand, you register one URL. From that moment on, every time a lead is created or changes in Nexus, we send it to you automatically — usually within about a minute.
Quick Start
1. Get a URL from your CRM
Most tools give you a "webhook" or "incoming request" URL:
- Zoho Flow — create a Flow, pick Webhook as the trigger, copy the URL it shows you
- n8n — add a Webhook node and copy its Production URL
- Make / Zapier — create a scenario or Zap starting with Webhook / Catch Hook
- Your own server — any HTTPS endpoint that accepts a
POSTwith a JSON body - Bitrix24 — connects directly, no automation tool needed. See Bitrix24 below
2. Register it in Nexus
- Go to Settings > Webhooks
- Click Add endpoint
- Fill in:
- Name — anything that helps you recognize it, e.g.
Zoho Sync - Endpoint URL — the URL you copied in step 1
- Events — which changes you want to receive (see Events below)
- Name — anything that helps you recognize it, e.g.
- Click Create
3. Send a test
Open the endpoint you just created and click Send test. Nexus immediately sends a sample lead and shows you exactly what your server replied — status code, response body and how long it took.
If you see a green result, you are done. Your CRM will now receive real leads as they happen.
Bitrix24
Using Bitrix24? Nexus writes every lead straight into your Bitrix24 CRM. You don't need Zapier, Make, n8n or a developer.
1. Create the link in Bitrix24
- In Bitrix24, open Developer resources > Other > Inbound webhook
- Under Assign permissions, add CRM (crm)
- Click Save, then copy the Webhook to call REST API link. It looks like
https://yourcompany.bitrix24.com/rest/1/abc123xyz/
Treat this link like a password. Anyone who has it can add and change records in your CRM. Don't paste it in chats or screenshots. If it leaks, click Generate new in Bitrix24.
2. Add it in Nexus
- Go to Settings > Webhooks and click Add endpoint
- Under Send leads to, choose Bitrix24
- Paste the link and click Create
The secret part of the link is hidden as soon as you save it.
3. Test the connection
Click Test connection. This only reads from Bitrix24 and never creates a lead.
| You see | What to do |
|---|---|
| Connected | Nothing. New leads will start arriving |
| No CRM permission | In Bitrix24, edit the webhook, add CRM (crm) under permissions, and save |
| Bitrix24 rejected the link | The link is wrong or was deleted. Copy it again from Bitrix24 and click Change link |
| Simple CRM mode | Your Bitrix24 has leads turned off, so each lead becomes a deal automatically |
What arrives in Bitrix24
| Bitrix24 field | From Nexus |
|---|---|
| Title | The customer's name, followed by "— JackAI" |
| Name | The customer's name |
| Phone | The number in international form, e.g. +966501234567 |
| The customer's email | |
| Country | The two-letter country code, e.g. SA |
| Comments | What the customer is asking for (the AI summary), their interests, your custom fields, status, score and assigned agent |
| Source description | JackAI Nexus |
- Changes update the same lead. When a phone or email changes, Bitrix24 shows the new value instead of a second one.
- Nothing is ever deleted in Bitrix24. If your team deletes a lead in Bitrix24, Nexus leaves it deleted and does not add it back.
- Leads that existed before you connected reach Bitrix24 the first time they change.
- If you generate a new link in Bitrix24, open the endpoint and click Change link. Leads already sent stay linked as long as it's the same Bitrix24 account.
Bitrix24 receives new leads (lead.created) and their updates (lead.updated). The signature section below doesn't apply to Bitrix24, because the secret link itself authorizes each request.
Events
Choose which changes you want to receive. Most integrations subscribe to lead.created and lead.updated.
| Event | When it fires |
|---|---|
lead.created | A new lead enters Nexus — from a conversation, an import, or created by hand |
lead.updated | A lead's details change — score, status, name, email, assigned agent, custom fields |
lead.deleted | A lead was deleted |
lead.merged | Two leads turned out to be the same person and were merged into one |
About lead.merged
Nexus recognizes the same customer across WhatsApp, Messenger, Instagram and web chat by phone number. When it discovers that two leads are the same person, it merges them and sends lead.merged. The payload carries the id that disappeared plus a merged_into field with the id that survived, so you can merge the matching records on your side too.
What we send
Every request is a POST with a JSON body in this shape:
{
"type": "lead.updated",
"timestamp": "2026-08-10T14:03:22+00:00",
"data": {
"id": "6a0e44b1-6d3c-4f9a-9b2e-2f1c8a7d4e10",
"name": "Ahmad Al-Sayed",
"phone": "966501234567",
"phone_e164": "+966501234567",
"email": "ahmad@example.com",
"country": "SA",
"status": "interested",
"score": 72,
"summary": "Asking about 3-bedroom units in the Yasmin project.",
"interests": ["apartments", "yasmin-project"],
"source": "conversation",
"pipeline_excluded": false,
"whatsapp_optout": false,
"custom_fields": { "project_interest": "Yasmin" },
"assigned_to": {
"id": "7c1f...",
"name": "Sara Ahmed",
"email": "sara@yourcompany.com"
},
"created_at": "2026-08-10T13:58:02+00:00",
"updated_at": "2026-08-10T14:03:20+00:00",
"changed_fields": ["status", "score"]
}
}
Fields
| Field | Meaning |
|---|---|
id | The lead's permanent id in Nexus. Store it — it is how you match updates to the record you already created |
name, email | May be empty until the customer provides them |
phone | Digits only, no + or 00 — e.g. 966501234567 |
phone_e164 | The same number in +966501234567 form, if available |
status | The lead status from Settings > Lead statuses — your own configured values |
score | 0–100, set by the AI from the conversation |
summary | A short AI summary of what the customer wants |
source | conversation, import or manual |
custom_fields | Your organization's custom fields |
assigned_to | The agent handling this lead, or null |
changed_fields | On lead.updated only — which fields changed. Useful if you only care about some of them |
Note: the payload is a snapshot of the lead at the moment we sent it. For the authoritative current state, treat the Nexus dashboard as the source of truth.
Delivery rules
Two rules to build against. Getting these right prevents duplicate and out-of-order records in your CRM.
At least once
We guarantee a webhook arrives at least once — occasionally the same event arrives twice (for example if your server received it but the reply was lost on the way back).
What to do: every request has a webhook-id header that stays the same across retries. Keep a note of the ids you have already processed and ignore repeats.
Order is not guaranteed
Two changes to the same lead may arrive in the opposite order.
What to do: compare the timestamp in the body and keep the newest. Do not assume the last request to arrive is the most recent change.
Grouped updates
While a customer is chatting, the AI may update the lead many times in a few seconds. We deliberately group those into a single lead.updated rather than sending dozens — so your CRM is not flooded and your rate limits are not burned. changed_fields lists everything that changed in the group.
Verifying the signature
Every request is signed so you can prove it came from Nexus and not from someone else who guessed your URL.
We follow the Standard Webhooks specification, which means you can verify with an off-the-shelf library instead of writing the logic yourself.
The headers
webhook-id: evt_9f3a1c4b8d2e4f6a9c1b3d5e7f2a4c6b
webhook-timestamp: 1786370640
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
With a library (recommended)
pip install standardwebhooks
from standardwebhooks import Webhook
# From Settings > Webhooks — store it as a secret, never in your source code
secret = "whsec_..."
def handle_request(headers, raw_body: bytes):
wh = Webhook(secret)
payload = wh.verify(raw_body, headers) # raises if the signature is invalid
lead = payload["data"]
print(lead["id"], lead["status"], lead["score"])
return "", 200
JavaScript works the same way:
npm install standardwebhooks
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.NEXUS_WEBHOOK_SECRET);
const payload = wh.verify(rawBody, headers); // throws if invalid
Verifying manually
If you prefer not to add a dependency, the signature is an HMAC-SHA256 of the id, timestamp and raw body joined by full stops:
import base64, hashlib, hmac
def is_valid(secret: str, headers, raw_body: bytes) -> bool:
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
signed_content = f"{msg_id}.{timestamp}.{raw_body.decode()}".encode()
key = base64.b64decode(secret.removeprefix("whsec_"))
expected = base64.b64encode(
hmac.new(key, signed_content, hashlib.sha256).digest()
).decode()
# The header may carry several space-separated signatures during rotation
for entry in headers["webhook-signature"].split(" "):
version, _, signature = entry.partition(",")
if version == "v1" and hmac.compare_digest(signature, expected):
return True
return False
Two things to get right:
- Sign the raw request body, exactly as received. If your framework parses the JSON and you re-serialize it, the bytes change and the signature will never match.
- Use a constant-time comparison (
hmac.compare_digest), not==.
Rejecting replays
Also check that webhook-timestamp is within a few minutes of your current time — we recommend 5 minutes. This stops someone from capturing a valid request and replaying it later.
Rotating the secret
If your secret is ever exposed, open the endpoint and click Rotate secret.
The old secret keeps working for 24 hours so you have time to deploy the new one without missing any leads. During that window we sign each request with both, and the webhook-signature header carries two space-separated signatures — the verification libraries handle this automatically.
Responding correctly
Reply with a 2xx status as soon as you have safely stored the event. Do the slow work — writing to your CRM, sending emails — after you reply.
@app.post("/nexus-webhook")
def nexus_webhook():
payload = verify_and_parse(request)
queue.enqueue(process_lead, payload) # do the real work in the background
return "", 200 # reply immediately
Requests time out after 10 seconds. A slow endpoint looks like a failed one and gets retried unnecessarily.
Which replies mean what
| Your reply | What we do |
|---|---|
2xx | Success — done |
429, 5xx, timeout, connection error | Retry with increasing delays for up to 24 hours |
Other 4xx (400, 401, 404, 410) | Treated as permanent — no retry. Use these only when you truly want us to stop |
3xx redirect | Treated as a failure. Register the final URL directly instead |
When something goes wrong
Recent deliveries
Open your endpoint and click Show recent deliveries. Every attempt is listed with the status code, the response body we received and how long it took — usually enough to see the problem without touching your own logs.
Automatic retries
A failed delivery is retried with growing gaps (seconds, then minutes, then hours) for up to 24 hours. A brief outage on your side loses nothing.
Automatic disabling
If an endpoint keeps failing over a longer period, we disable it and email your organization's admins. Nothing is silently dropped — you always find out.
To bring it back: fix the endpoint, then open Settings > Webhooks and switch it back on. The failure counter resets.
Common causes
| What you see | Usually means |
|---|---|
404 on every attempt | The URL changed, or the scenario in your CRM was deleted |
401 / 403 | Your tool expects an auth token the URL no longer carries |
| Signature never matches | You are signing the parsed-and-re-serialized JSON instead of the raw body |
| The same lead arrives twice | Normal — dedupe on webhook-id |
| Fields look out of date | An older event arrived late — compare timestamp and keep the newest |
Security notes
- Treat the signing secret like a password. Store it in your environment or secret manager, never in your source code.
- HTTPS only. We do not deliver to plain
http://addresses. - Public addresses only. Endpoints on internal or private networks are rejected, in both directions.
- Verify the signature. Your URL is not a secret — anyone who learns it could post to it. The signature is what proves a request really came from us.
Frequently asked questions
Do I need a developer? Not for the basics. Zoho Flow, n8n, Make and Zapier can receive the webhook and map the fields with no code. A developer is only needed for signature verification, which is optional with those tools since your webhook URL is already private.
Does this affect my customers or my AI replies? No. Webhooks only read what already happened. Registering one changes nothing about how the AI talks to customers.
Will it send my existing leads? No. Only changes from the moment you create the endpoint. Use Leads > Export for a one-off copy of what you already have.
Do you support Bitrix24? Yes, directly. Choose Bitrix24 when you add the endpoint and paste the inbound webhook link from Bitrix24. See Bitrix24.
Can I send to more than one system? Yes. Add an endpoint for each. They are delivered independently, so one being down does not affect the others.
How fast is it? Normally within about a minute of the change. Updates during an active conversation are grouped, so they arrive slightly later but as a single, complete update.