Elite feature

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.

Real-time

Payload delivered within seconds of lead insertion.

Signed

HMAC-SHA256 signature on every request. Verify authenticity server-side.

Any URL

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.

JSON example
{
  "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
  }
}
FieldTypeDescription
eventstringAlways lead.created
timestampnumberUnix milliseconds when the event was generated
data.idnumberInternal lead ID
data.case_numberstringWCCA case number, e.g. 2026PR000031
data.lead_typestringOne of: probate, divorce, foreclosure, eviction
data.countystringWisconsin county name, e.g. Brown
data.statestringAlways WI (Wisconsin)
data.owner_namestringName of the primary party on the filing (may be owner, decedent, defendant, respondent, or any party; not necessarily a property owner)
data.property_addressstringAddress associated with the filing (may not be owned by the named party)
data.mailing_addressstring | nullMailing address if different from property
data.filing_datestringWCCA filing date in YYYY-MM-DD format
data.statusstringAlways new on creation
data.created_atstringISO-8601 timestamp when the lead was inserted
data.primary_phonestring | nullPhone number, included when an auto-skiptrace filter matched. null if not found or skiptrace not triggered.
data.mobile_1 … mobile_5string | nullAdditional mobile numbers from skiptrace, if available
data.landline_1 … landline_3string | nullLandline numbers from skiptrace, if available
data.email_1 … email_5string | nullEmail addresses from skiptrace, if available
data.dnc_primary_phonestring | nullDNC 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_5string | nullDNC flags for each mobile number. Same format: "" = clean, comma-separated flags = flagged, null = not checked.
data.dnc_landline_1 … dnc_landline_3string | nullDNC flags for each landline. Same format as above.
data.skiptrace_no_resultboolean | undefinedtrue 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:

Header
X-Webhook-Signature: t=1740000000000,v1=a3f2...c9d1

The signature is constructed as:

  1. Extract t (timestamp in ms) and v1 (HMAC) from the header.
  2. Build the signed string: ${t}.${rawBody}(timestamp, literal dot, raw JSON body, no newlines or extra whitespace).
  3. Compute HMAC-SHA256(secret, signedString) and compare to v1 using a constant-time equality function.
  4. Optionally reject payloads where |now - t| > 300_000 ms (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

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

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
<?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

ZapierWorks today

Use the "Webhooks by Zapier" trigger (Catch Hook). Requires Zapier Professional plan or higher. See the step-by-step guide below.

Make (Integromat)Works today

Use a "Webhooks" module as the trigger in any Make scenario.

n8nWorks today

Add a "Webhook" node as your workflow trigger.

Follow Up BossVia Zapier

Connect via Zapier: Leads by Alex webhook → FUB Create Person action.

HubSpotVia Zapier/Make

Create contacts or deals in HubSpot from every new lead.

Custom serverAny language

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

    Copy the Zapier webhook URL

    Zapier shows you a URL like https://hooks.zapier.com/hooks/catch/…/…/. Copy that URL.

  3. 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. 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. 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 nameDescription
    eventAlways lead.created
    data__idInternal lead ID
    data__case_numberWCCA case number, e.g. 2026PR000031
    data__lead_typeprobate / divorce / foreclosure / eviction
    data__countyWisconsin county name
    data__owner_nameName of the primary party on the filing
    data__property_addressAddress associated with the filing
    data__mailing_addressMailing address (may be null)
    data__filing_dateYYYY-MM-DD filing date
    data__primary_phoneBest phone number from skiptrace (if enriched)
    data__mobile_1Mobile phone from skiptrace (if enriched)
    data__email_1Email address from skiptrace (if enriched)
    data__dnc_primary_phoneDNC flags for primary phone: "clean" = not on any list, "national_dnc,litigator" etc = flagged (if auto-DNC fired)
  6. 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 →