Skip to main content

Using Webhooks to Send Your Lead Events to Any CRM or Tool

Get notified the moment a lead comes in, RSVPs, attends a webinar, or gets enriched — sent straight to Zapier, Make, your CRM, or any tool. Includes a one-click test so you can set it up without waiting for a real lead.

Written by Chris Ross

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 webhookAdd, 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:

  1. Paste the address you just copied.

  2. Tick the events you want (start with just one or two — you can change this later).

  3. Click Create endpoint.

  4. 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.

  1. In Zapier or Make, put the webhook step into "listening" mode.

  2. Back in the portal, find your endpoint and use the Test row: pick an event type and click Send test event.

  3. 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.

An active webhook endpoint showing its subscribed events, the signing secret with Reveal, Copy and Rotate buttons, and the Test row with an event picker and Send test event 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

lead.created

A new lead comes in

lead.registration_outcome

The lead is qualified, confirmed, or declined for an event

lead.rsvp

The lead responds to an RSVP (confirmed or declined)

lead.webinar_attended

The lead attends (or partially attends) a webinar

lead.webinar_no_show

The lead registered for a webinar but did not attend

lead.enriched

New enrichment data becomes available for the lead

lead.sms_opted_out

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:

  • testtrue on events you sent yourself from the Test row, false on real ones. Have your automation skip the true ones.

  • 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 https and 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.

The Recent deliveries list showing delivered and pending events, each with a Resend button, and a Refresh button

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

lead_priority

"high"

Our overall priority for the lead: high · medium-high · medium · very-low · unknown.

client_avatar / client_avatar_label

"legacy_retiree" / "Legacy Retiree"

A persona we assign from the enrichment data. The _label is the human-readable version. Examples: Legacy Retiree, Business Entrepreneur, Multi-Property Professional, Practical Protector.

is_married

true

true or false.

household_net_worth_bucket

"$250,000 - $499,999"

A range, as a string.

household_income_bucket

"$200,000 - $249,999"

A range, as a string.

household_home_value_bucket

"$500,000 - $749,999"

A range, as a string.

household_property_count

1

How many properties we can tie to the household. A number.

wealth_signal_conflict

false

true when our sources disagree about net worth — see below.

net_worth_floor_from_assets

450095

Only present when wealth_signal_conflict is true. A conservative floor, in dollars, from the household's visible property and assets.

enrichment_status

"complete"

How much we could fill in: complete · professional_only · consumer_only · no_property · insufficient_data.

enriched_at

"2026-07-19T04:10:22Z"

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

call_scheduled_at

"2026-07-21T14:32:07Z"

Set when you tell us the lead booked. Timestamps are UTC.

converted_at

"2026-07-28T16:05:00Z"

Set when you tell us they became a client.

conversion_value

12500

A number, not a string. No currency symbol.

quality_rating

"qualified"

qualified or unqualified. Older records may still show thumbs_up / thumbs_down.

sms_opted_out_at

"2026-07-22T09:14:51Z"

Set when they text STOP.

registration_outcome

"Confirmed"

Confirmed or Declined. Only used on accounts where we screen registrations.

event_venue

"Lakewood Branch Library"

Seminars only — null for webinars.

rsvp_status

"confirmed"

confirmed · declined · pending · expired (lowercase).

attendance.status

"completed"

completed · left early · did not attend.

ac_tags

["Webinar: Elder Law"]

Always an array. [] when there are none.

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

X-EDM-Event

Unique event id (matches id in the body)

X-EDM-Delivery

Unique id for this delivery attempt — use it to ignore duplicates

X-EDM-Signature

sha256= followed by the signature

Content-Type

application/json

User-Agent

EDM-Webhooks/1

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-Delivery or the body's id.

  • 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.

Did this answer your question?