A webhook is just an automatic message we send the instant something happens to one of your leads — someone registers, RSVPs, attends a webinar, or new enrichment data lands. Instead of you checking the portal, your tools find out on their own and can act immediately.
What people actually use this for: adding every new lead to their CRM automatically · texting or emailing someone the moment they register · starting a follow-up sequence when a lead doesn't attend · alerting the team in Slack when a high-priority lead comes in.
You don't need a developer for this. If you can use Zapier or Make, you can set this up. There's a developer reference at the bottom, but you can safely ignore it.
How it works, in one line
You give us a web address, we send lead events to it. That address usually comes from Zapier or Make — they hand you one to paste.
Step 1 — Get an address to send to
Pick whichever tool you already use. Each gives you a URL to copy.
Zapier — create a new Zap and choose the trigger Webhooks by Zapier → Catch Hook. Zapier shows you a "Custom Webhook URL." Copy it. (Heads up: Webhooks by Zapier needs a paid Zapier plan.)
Make — create a scenario, add the Webhooks module → Custom webhook → Add, give it a name, and copy the address it generates.
GoHighLevel — create a workflow with the Inbound Webhook trigger and copy its URL.
n8n — add a Webhook node and copy its Production URL.
The address must start with https.
Step 2 — Tell us where to send
In your portal, go to Account → Webhooks and use Add an endpoint:
Paste the address you just copied.
Tick the events you want (start with just one or two — you can change this later).
Click Create endpoint.
Copy your signing secret somewhere safe. It's shown once. You only need it if a developer is verifying our messages — if you're using Zapier or Make, you can ignore it.
Step 3 — Send a test and see the data
This is the step people skip, and it's the one that makes everything else easy. You don't need to wait for a real lead — you can send yourself a test whenever you like.
In Zapier or Make, put the webhook step into "listening" mode.
Back in the portal, find your endpoint and use the Test row: pick an event type and click Send test event.
Your tool catches it and shows you every field we send. From there you just drag those fields wherever you want them — into your CRM, an email, a Slack message.
⏱️ Allow up to 60 seconds for the test to arrive. Delivery is queued, not instant — pressing the button again doesn't make it faster, it just queues another one (there's a limit of 20 test events per hour).
A test event is a real, signed request — the same delivery path, the same signature, the same shape as the live thing. Only the details are made up: the lead is called Jane Testlead, and the body carries "test": true so your automation can skip it. Real events carry "test": false.
Watch for it in Recent deliveries below the endpoint — it shows whether it worked, and gives you a Resend button.
Step 4 — Build the rest of your automation
Once your tool has caught one event, add whatever comes next: create the contact in your CRM, send a notification, start a sequence. Turn it on and you're done.
One thing to add: have your automation ignore any event where test is true, so your test runs don't create real records.
What you can be notified about
Event | When it fires |
| A new lead comes in |
| The lead is qualified, confirmed, or declined for an event |
| The lead responds to an RSVP (confirmed or declined) |
| The lead attends (or partially attends) a webinar |
| The lead registered for a webinar but did not attend |
| New enrichment data becomes available for the lead |
| The lead opts out of SMS |
Tip: only tick what you'll actually use. It keeps your automations cheaper and easier to follow.
Why booked calls, new clients and lead ratings aren't on this list. Those are things you tell us — you know a consultation was booked or a client signed long before we do. So they travel in the other direction: see Sending Us Your Conversion Data. (They still appear as fields inside the events above once you've sent them to us — see call_scheduled_at, converted_at and quality_rating in the developer reference.)
What's in each message
Every event includes the lead's name, email, phone, which event they registered for, and the date — plus their own answers from your registration form, and enrichment data when we have it. In Zapier or Make these show up as fields you can pick from a list; you don't need to read the format below unless you want to.
A few worth knowing:
test —
trueon events you sent yourself from the Test row,falseon real ones. Have your automation skip thetrueones.custom_fields — the lead's own answers from your registration form, exactly as submitted.
enrichment — a quick read on the lead when enrichment is enabled: priority, a persona, and household wealth indicators.
Anything that doesn't apply to a lead yet comes through empty.
Managing your endpoints
Send test event — fire a sample of any event you're subscribed to, any time, without waiting for a real lead.
Reveal / Copy / Rotate the signing secret any time. Rotating breaks the old one immediately, so update your receiver right after.
Disable / Enable to pause and resume delivery — useful while you're rebuilding an automation.
Delete an endpoint you no longer need.
Recent deliveries shows what we've sent, whether it worked, and a Resend button. It pages 8 at a time — use Older to look further back.
If something isn't working
Nothing is arriving — check the address starts with
httpsand was pasted in full, and that the endpoint isn't disabled. Then use Send test event and watch your tool.It says pending — normal. Delivery is queued, so give it up to 60 seconds, then click Refresh.
It says failed — your receiver didn't accept it. In Zapier/Make, make sure the webhook step is turned on and the Zap/scenario is published.
It stopped working — an endpoint that keeps failing is switched off automatically. Fix the receiver, then re-enable it here.
The same event arrived twice — that can happen when we retry. Your tool should treat a repeat of the same event id as a duplicate.
Good to know
Addresses must be HTTPS. Private or internal addresses are blocked.
You only ever receive your own company's lead events.
Keep your signing secret private.
Want to send data to us instead — booked calls, conversions, attendance, lead ratings? See Sending Us Your Conversion Data.
Developer reference
Everything below is optional. If you're using Zapier or Make, you can stop here.
The payload
Every webhook is a JSON POST with the same envelope; data holds the lead:
{
"id": "evt_a013abbeecfd44cb8...",
"type": "lead.created",
"occurred_at": "2026-07-19T03:22:43Z",
"company_id": "CID-XXXXXX",
"test": false,
"data": {
"lead_id": "785138da-f81c-4579-a63c-c2d591d31f20",
"company_id": "CID-XXXXXX",
"name": "Jane Sample",
"first_name": "Jane",
"last_name": "Sample",
"email": "[email protected]",
"phone": "(555) 123-4567",
"event_type": "webinar",
"event_title": "Estate Planning Essentials",
"event_date": "2027-05-25",
"event_time": "6:00 PM",
"event_venue": null,
"event_topic": "Estate Planning",
"created_at": "2026-07-19T03:22:43Z",
"registration_outcome": null,
"call_scheduled_at": null,
"converted_at": null,
"conversion_value": null,
"quality_rating": null,
"sms_opted_out_at": null,
"ac_tags": ["Webinar: Elder Law", "Web Elder Law 07.27.26"],
"enrichment": {
"lead_priority": "high",
"client_avatar": "legacy_retiree",
"client_avatar_label": "Legacy Retiree",
"is_married": true,
"household_net_worth_bucket": "$250,000 - $499,999",
"household_income_bucket": "$200,000 - $249,999",
"household_home_value_bucket": "$500,000 - $749,999",
"household_property_count": 1,
"wealth_signal_conflict": false,
"enrichment_status": "complete",
"enriched_at": "2026-07-19T04:10:22Z"
},
"custom_fields": {
"what_is_your_biggest_estate_planning_concern": "Protecting my kids' inheritance"
}
}
}
The test flag. Every envelope carries a top-level test boolean. It is false on real events and true on anything sent from the Send test event button in the portal. Test events are otherwise identical — same signature, same headers, same structure — so they're a genuine end-to-end check of your receiver. Gate on it before writing records:
if (event.test) return; // sample data — don't create a real contact
Test events use obviously fake details (Jane Testlead, [email protected], a 555 phone number) and their event id starts with evt_test_.
ac_tags lists the tag names on that lead's ActiveCampaign contact — your event tags (e.g. Web Elder Law 07.27.26) and any other tags on the record (Webinar: Elder Law, SMS Follow Up). It is always an array: [] means we haven't read tags for that lead yet, or the contact has none. Tag names come through exactly as they are spelled in ActiveCampaign.
Inside the enrichment object
For firms with lead enrichment turned on, we attach a short summary of what we've learned about the lead. The block above is what a fully-enriched lead looks like. Every key is optional — we include a key only when we have a value for it, so a lightly-enriched lead may carry just a few. Keys are left out entirely rather than sent as null.
Field | Example | What it is |
|
| Our overall priority for the lead: |
|
| A persona we assign from the enrichment data. The |
|
|
|
|
| A range, as a string. |
|
| A range, as a string. |
|
| A range, as a string. |
|
| How many properties we can tie to the household. A number. |
|
|
|
|
| Only present when |
|
| How much we could fill in: |
|
| When we last enriched the lead (UTC). |
About wealth_signal_conflict. Our data sources sometimes cap or understate a household's net worth even when the property and asset data clearly point higher. When we spot that, we set wealth_signal_conflict to true and add net_worth_floor_from_assets — a conservative floor based on assets we can actually see. If you route high-value leads automatically, trust the floor over the bucket whenever this flag is set.
Extra fields on RSVP and webinar events
Two event types add an object on top of everything above.
lead.rsvp adds:
"rsvp_status": "confirmed",
"rsvp_response_at": "2026-07-21T14:32:07Z"
lead.webinar_attended and lead.webinar_no_show both add:
"attendance": {
"attended": true,
"status": "completed",
"session_id": "2ab83a46-ca57-4a04-8837-e92e2c6607c9",
"participant_name": "Diane",
"reported_at": "2026-06-24T13:00:01Z"
}
On a no-show, the same object comes through with "attended": false and "status": "did not attend". participant_name is the name the person used to join the webinar, which is why it can differ from the name they registered with.
What the fields look like when they aren't empty
Most top-level fields arrive as null until the thing they describe actually happens. Here's what to expect once they're filled in. Note that call_scheduled_at, converted_at, conversion_value and quality_rating are filled from the data you send us — see Sending Us Your Conversion Data.
Field | Example value | Notes |
|
| Set when you tell us the lead booked. Timestamps are UTC. |
|
| Set when you tell us they became a client. |
|
| A number, not a string. No currency symbol. |
|
|
|
|
| Set when they text STOP. |
|
|
|
|
| Seminars only — |
|
|
|
|
|
|
|
| Always an array. |
The enrichment object follows its own rule — its keys are left out when we don't have them, rather than sent as null (see the enrichment table above). And custom_fields is a passthrough of that lead's own form answers, so its keys differ from one campaign to the next.
Headers
Header | Value |
| Unique event id (matches |
| Unique id for this delivery attempt — use it to ignore duplicates |
|
|
|
|
|
|
Verifying the signature
X-EDM-Signature is sha256= followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret, hex-encoded. Compute it and compare. Hash the raw body exactly as received — don't parse and re-serialize the JSON first, or it won't match.
// Node.js
const crypto = require('crypto');
const expected =
'sha256=' + crypto.createHmac('sha256', SECRET)
.update(rawBody)
.digest('hex');
// compare `expected` to the X-EDM-Signature header
Test events are signed exactly the same way, so Send test event is a real check of your verification code.
Retries
Reply with a 2xx. If you don't, we retry with increasing delays, up to 6 attempts.
Because of retries the same event can arrive twice — dedupe on
X-EDM-Deliveryor the body'sid.Persistently failing endpoints are disabled automatically; re-enable in the portal.
Resend replays the original saved payload.
Delivery is queued, so allow up to 60 seconds. Test events are capped at 20 per hour.
Need a hand? Start a chat from the portal — tell us which tool you're connecting and we'll walk you through it.



