Webhook Integration
Leads by Alex fires a signed HTTP POST to your endpoint every time a new lead is added. Connect to Zapier, Make, HubSpot, Follow Up Boss, or any custom system. No polling required.
Payload delivered within seconds of lead insertion.
HMAC-SHA256 signature on every request. Verify authenticity server-side.
Works with Zapier, Make, n8n, or your own server.
Overview
When a new lead matches your subscription and is inserted into the database, Leads by Alex sends a POST request to every active endpoint you have configured.
Each request contains a JSON body (the payload) and an X-Webhook-Signature header you can use to verify the request came from Leads by Alex.
You can optionally attach a filter to an endpoint so it only fires for leads that match specific counties or lead types (probate, divorce, foreclosure, eviction).
If you enable auto-skiptrace on a saved filter, any matching lead will be skiptraced via Tracerfy before the webhook fires. Phone numbers and email addresses are included in the data object. One Tracerfy call is made per unique lead; all users whose filters match share the result and are each charged at their plan rate. A refund is issued automatically if Tracerfy returns no results.
If you also enable auto-DNC on a saved filter, each phone number returned by the skiptrace is checked against the National Do Not Call registry. The DNC result for each number is included in the payload as dnc_* fields. An empty string ("") means the number is clean. A non-empty string is a comma-separated list of the specific flags that fired, e.g. "national_dnc,litigator". Possible flags: national_dnc, state_dnc, dma, litigator. DNC credits are charged separately.
Payload schema
All requests use Content-Type: application/json.
{
"event": "lead.created",
"timestamp": 1740000000000,
"data": {
"id": 12345,
"case_number": "2026PR000031",
"lead_type": "probate",
"county": "Brown",
"state": "WI",
"owner_name": "John Smith",
"property_address": "123 Main St, Green Bay, WI 54301",
"mailing_address": "456 Oak Ave, Green Bay, WI 54301",
"filing_date": "2026-02-01",
"status": "new",
"created_at": "2026-02-01T12:00:00.000Z",
// Present only when a matching auto-skiptrace filter fired:
"primary_phone": "(920) 555-0100",
"mobile_1": "(920) 555-0101",
"mobile_2": null,
"landline_1": null,
"email_1": "john.smith@example.com",
"email_2": null,
// Present only when a matching auto-DNC filter also fired:
"dnc_primary_phone": "clean",
"dnc_mobile_1": "national_dnc,litigator",
"dnc_mobile_2": null,
"dnc_landline_1": null,
// true when skiptrace was attempted but Tracerfy returned no results:
"skiptrace_no_result": true
}
}| Field | Type | Description |
|---|---|---|
| event | string | Always lead.created |
| timestamp | number | Unix milliseconds when the event was generated |
| data.id | number | Internal lead ID |
| data.case_number | string | WCCA case number, e.g. 2026PR000031 |
| data.lead_type | string | One of: probate, divorce, foreclosure, eviction |
| data.county | string | Wisconsin county name, e.g. Brown |
| data.state | string | Always WI (Wisconsin) |
| data.owner_name | string | Name of the primary party on the filing (may be owner, decedent, defendant, respondent, or any party; not necessarily a property owner) |
| data.property_address | string | Address associated with the filing (may not be owned by the named party) |
| data.mailing_address | string | null | Mailing address if different from property |
| data.filing_date | string | WCCA filing date in YYYY-MM-DD format |
| data.status | string | Always new on creation |
| data.created_at | string | ISO-8601 timestamp when the lead was inserted |
| data.primary_phone | string | null | Phone number, included when an auto-skiptrace filter matched. null if not found or skiptrace not triggered. |
| data.mobile_1 … mobile_5 | string | null | Additional mobile numbers from skiptrace, if available |
| data.landline_1 … landline_3 | string | null | Landline numbers from skiptrace, if available |
| data.email_1 … email_5 | string | null | Email addresses from skiptrace, if available |
| data.dnc_primary_phone | string | null | DNC flags for the primary phone. Present only when auto-DNC fired. "" = clean; a non-empty string is a comma-separated list of active flags (e.g. "national_dnc,litigator"). null if number was not found or DNC not triggered. |
| data.dnc_mobile_1 … dnc_mobile_5 | string | null | DNC flags for each mobile number. Same format: "" = clean, comma-separated flags = flagged, null = not checked. |
| data.dnc_landline_1 … dnc_landline_3 | string | null | DNC flags for each landline. Same format as above. |
| data.skiptrace_no_result | boolean | undefined | true when a skiptrace was attempted but Tracerfy returned no phones or emails; absent when not attempted or when results were found |
Signature verification
Always verify the signature before processing a webhook. This prevents attackers from forging requests to your endpoint.
Every request includes an X-Webhook-Signature header:
X-Webhook-Signature: t=1740000000000,v1=a3f2...c9d1The signature is constructed as:
- Extract
t(timestamp in ms) andv1(HMAC) from the header. - Build the signed string:
${t}.${rawBody}(timestamp, literal dot, raw JSON body, no newlines or extra whitespace). - Compute
HMAC-SHA256(secret, signedString)and compare tov1using a constant-time equality function. - Optionally reject payloads where
|now - t| > 300_000ms (5 minutes) to prevent replay attacks.
Your per-endpoint secret is shown in the Automations dashboard. Treat it like a password. Never expose it client-side.
Code examples
Node.js / TypeScript
import crypto from "crypto";
export function verifyWebhookSignature(
rawBody: string,
signature: string, // from X-Webhook-Signature header
secret: string,
): boolean {
// Parse "t=<ms>,v1=<hmac>"
const parts = Object.fromEntries(
signature.split(",").map((p) => p.split("=", 2))
);
const timestamp = parts["t"];
const hmac = parts["v1"];
if (!timestamp || !hmac) return false;
// Reject replays older than 5 minutes
if (Date.now() - Number(timestamp) > 5 * 60 * 1000) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(expected),
);
}Python
import hmac
import hashlib
import time
def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
"""Verify the X-Webhook-Signature header from Leads by Alex."""
# Parse "t=<ms>,v1=<hmac>"
parts = dict(p.split("=", 1) for p in signature.split(",") if "=" in p)
timestamp = parts.get("t")
received_hmac = parts.get("v1")
if not timestamp or not received_hmac:
return False
# Reject replays older than 5 minutes
if abs(time.time() * 1000 - int(timestamp)) > 5 * 60 * 1000:
return False
signed_payload = f"{timestamp}.{raw_body.decode('utf-8')}"
expected = hmac.new(
secret.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(received_hmac, expected)PHP
<?php
function verifyWebhookSignature(
string $rawBody,
string $signature,
string $secret
): bool {
// Parse "t=<ms>,v1=<hmac>"
$parts = [];
foreach (explode(',', $signature) as $part) {
[$k, $v] = explode('=', $part, 2);
$parts[$k] = $v;
}
if (empty($parts['t']) || empty($parts['v1'])) return false;
// Reject replays older than 5 minutes
if (abs(microtime(true) * 1000 - (int)$parts['t']) > 5 * 60 * 1000) {
return false;
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1']);
}Integrations
Use the "Webhooks by Zapier" trigger (Catch Hook). Requires Zapier Professional plan or higher. See the step-by-step guide below.
Use a "Webhooks" module as the trigger in any Make scenario.
Add a "Webhook" node as your workflow trigger.
Connect via Zapier: Leads by Alex webhook → FUB Create Person action.
Create contacts or deals in HubSpot from every new lead.
Any HTTPS endpoint works. See the code examples above for signature verification.
Zapier quick-start
Zapier plan required: “Webhooks by Zapier” is a premium app that requires a Zapier Professional plan or higher (paid). It is not available on the free tier.
- 1
Create a new Zap with a Webhook trigger
In Zapier, click Create Zap. For the Trigger, search for Webhooks by Zapier and select the event Catch Hook. Click Continue and Zapier will generate a unique webhook URL for this Zap.
- 2
Copy the Zapier webhook URL
Zapier shows you a URL like
https://hooks.zapier.com/hooks/catch/…/…/. Copy that URL. - 3
Add the URL as an endpoint in Leads by Alex
Go to Automations → Add endpoint. Paste the Zapier URL into the URL field. Optionally attach a filter so the webhook only fires for specific counties or lead types. Save the endpoint.
- 4
Send a test payload to populate Zapier fields
Click the Test button on your new endpoint (or use the Webhook Inspector). This fires a real sample payload to Zapier. Back in Zapier, click “Test trigger”, and Zapier will find the test request and auto-detect every field.
- 5
Map fields in your Zapier action
Because the payload is nested JSON, Zapier flattens nested keys using double-underscore (
__). Here are the most useful field names you'll see in Zapier's field picker:Zapier field name Description event Always lead.created data__id Internal lead ID data__case_number WCCA case number, e.g. 2026PR000031 data__lead_type probate / divorce / foreclosure / eviction data__county Wisconsin county name data__owner_name Name of the primary party on the filing data__property_address Address associated with the filing data__mailing_address Mailing address (may be null) data__filing_date YYYY-MM-DD filing date data__primary_phone Best phone number from skiptrace (if enriched) data__mobile_1 Mobile phone from skiptrace (if enriched) data__email_1 Email address from skiptrace (if enriched) data__dnc_primary_phone DNC flags for primary phone: "clean" = not on any list, "national_dnc,litigator" etc = flagged (if auto-DNC fired) - 6
Add your action(s) and turn on the Zap
Common actions to connect to leads:
- Google Sheets: Append a row for each new lead. Great for a running log or mail merge.
- Follow Up Boss: Create a new Person contact; map data__owner_name → Full Name, data__primary_phone → Phone.
- HubSpot: Create a Contact or Deal; map fields to HubSpot properties.
- Gmail / Outlook: Send yourself or your team an email notification with lead details.
- SMS (Twilio / ClickSend): Text yourself the name, address, and phone number the moment a lead fires.
- Slack / Teams: Post a message to a channel with a link to the lead.
Tip: Filter inside Zapier. Add a Zapier Filter step after the trigger if you want to route different lead types to different actions. For example: only continue if data__lead_type exactly matches probate. Alternatively, create separate endpoints in Leads by Alex (one per lead type), each with its own Zap.
Best practices
Respond quickly
Return HTTP 200 within 10 seconds. Leads by Alex retries once after 2 seconds on failure. If both attempts fail, the delivery is logged in your Automations dashboard so you can inspect and resend it. If your processing takes longer, queue the payload and process it asynchronously.
Verify signatures
Always verify the X-Webhook-Signature before processing. Reject requests with an invalid or missing signature.
Guard against replays
Reject payloads where |now - timestamp| > 300 000 ms (5 minutes). Store processed event timestamps or IDs to deduplicate retries.
Use HTTPS endpoints only
HTTP URLs are rejected at save time. Use a valid TLS certificate. Self-signed certificates are not accepted.
Test before going live
Each endpoint has a "Test" button in the Automations dashboard that sends a synthetic lead.created payload. Confirm delivery before relying on live data.
Keep your secret safe
Treat your webhook secret like a password. Rotate it by editing the endpoint. Never log or expose it in client-side code.
Monitor delivery failures
Failed deliveries are logged in the Delivery Failures panel on the Automations page. Use the Resend button to replay a failed payload once your endpoint is back online. Rows are removed automatically when a resend succeeds.
Ready to connect?
Add your first endpoint in the Automations dashboard. Use the Test button to verify delivery instantly.
Configure endpoints →