Skip to main content
Webhooks let your application react to changes in LeadLex the moment they happen. Register an HTTPS endpoint once, and LeadLex will POST a signed JSON payload to it whenever a subscribed event fires — no polling required.
Webhooks are production-ready. All payloads are signed with HMAC-SHA256, retried with exponential backoff on failure, and protected against SSRF by blocking private IP ranges.

How It Works

  1. You register a webhook endpoint by calling POST /v1/webhooks with a target URL and a list of events.
  2. LeadLex returns a signing secret (shown once — store it securely).
  3. When a subscribed event occurs, LeadLex sends a POST request to your URL with a signed JSON body.
  4. Your endpoint must respond with a 2xx status within 10 seconds. Otherwise the delivery is retried with exponential backoff.

Event Types

LeadLex fires 28 distinct event types across all core resources. Subscribe to as many (or as few) as you need.

Contacts

contact.created
contact.updated
contact.deleted
contact.bulk_created

Companies

company.created
company.updated
company.deleted

Deals

deal.created
deal.updated
deal.deleted
deal.bulk_created
deal.bulk_updated

Lists

list.created
list.deleted
list.contacts_added
list.contacts_removed

Campaigns

campaign.created
campaign.started
campaign.paused
campaign.deleted
campaign.step_created
campaign.contacts_added

Activities & Tasks

activity.created
task.created
task.updated
task.approved
task.dismissed

Delivery Headers

Every webhook POST from LeadLex includes the following headers:
HeaderDescription
X-LeadLex-Signaturesha256=<hex> HMAC of the raw request body using your webhook’s signing secret.
X-LeadLex-TimestampISO 8601 timestamp of when the delivery was generated. Use to guard against replay attacks.
X-LeadLex-Delivery-IDUnique UUID for this delivery attempt. Use for idempotency in your handler.
Content-TypeAlways application/json.
User-AgentLeadLex-Webhook/1.0

Payload Structure

All webhook payloads share a common envelope:
{
  "event": "contact.created",
  "delivery_id": "7f3c4d2a-1b8e-4a9c-9d6f-2e5b8c7a1f3d",
  "occurred_at": "2026-04-17T14:23:05Z",
  "workspace_id": "w_9a8b7c6d5e4f3a2b1c0d",
  "data": {
    "contact": {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "full_name": "Jane Doe",
      "email": "jane@example.com",
      "created_at": "2026-04-17T14:23:05Z"
    }
  }
}
The data object shape depends on the event type. For *.bulk_* events, data contains an array plus a count field.

Verifying Signatures

Always verify the signature before trusting a webhook. An attacker who discovers your webhook URL could otherwise forge events. Reject any request where X-LeadLex-Signature does not match.
To verify, compute HMAC-SHA256(secret, raw_body) and compare it to the hex value in X-LeadLex-Signature (after stripping the sha256= prefix). Use a constant-time comparison to prevent timing attacks.
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_signing_secret_here"

def verify_signature(raw_body: bytes, signature_header: str) -> bool:
    if not signature_header or not signature_header.startswith("sha256="):
        return False
    received = signature_header.split("=", 1)[1]
    expected = hmac.new(
        WEBHOOK_SECRET.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(received, expected)

@app.post("/leadlex/webhook")
def handle_webhook():
    raw = request.get_data()
    sig = request.headers.get("X-LeadLex-Signature", "")

    if not verify_signature(raw, sig):
        abort(401, "invalid signature")

    payload = request.get_json()
    event_type = payload["event"]

    # Dispatch based on event
    if event_type == "contact.created":
        handle_contact_created(payload["data"]["contact"])

    return "", 200
Parse the raw body before JSON.parse (JS) or request.get_json() (Python). Signatures are computed over the exact bytes sent — any re-serialization will break verification.

Retries & Delivery Guarantees

If your endpoint does not respond with 2xx within 10 seconds, LeadLex retries the delivery with exponential backoff:
AttemptDelay
1immediate
2+30s
3+2m
4+10m
5+1h
6+6h
After the final failed attempt, the delivery is marked failed and no further retries occur. All attempts — successful or not — are recorded in the webhook logs (accessible via GET /v1/webhooks/{id}/logs).
Deliveries are at-least-once. Use the X-LeadLex-Delivery-ID header to deduplicate in your handler if exactly-once semantics matter.

Security

SSRF Protection. LeadLex blocks webhook URLs that resolve to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, IPv6 link-local). Attempts to register such URLs are rejected with 400 invalid_url.
Additional safeguards:
  • HTTPS requiredhttp:// URLs are rejected at registration time.
  • Timeout — deliveries hard-timeout at 10 seconds to prevent slowloris attacks on LeadLex workers.
  • Secret rotation — delete and recreate a webhook to rotate its signing secret.
  • Replay protection — reject requests where X-LeadLex-Timestamp is older than 5 minutes.

Managing Webhooks

Use the API to register, update, and inspect webhooks:

List webhooks

GET /v1/webhooks

Create webhook

POST /v1/webhooks

Update webhook

PATCH /v1/webhooks/{id}

Delete webhook

DELETE /v1/webhooks/{id}

Delivery logs

GET /v1/webhooks/{id}/logs

Testing Locally

During development, tunnel your local server to a public HTTPS URL:
# Using ngrok
ngrok http 3000
# -> https://abc123.ngrok-free.app

# Register the tunneled URL
curl -X POST https://data.leadlex.com/functions/v1/api-gateway/v1/webhooks \
  -H "Authorization: Bearer wbk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://abc123.ngrok-free.app/leadlex/webhook",
    "events": ["contact.created", "deal.updated"],
    "description": "Local dev tunnel"
  }'
Then trigger an event in the LeadLex UI (create a contact, update a deal) and watch your local server receive the signed payload.