LeadRails
Docs navigation

Building your first source

End-to-end walkthrough for a new LeadRails customer. ~30 minutes.

LeadRails diagram showing a lead event moving from a source out through routes to multiple destinations.
Illustrative LeadRails core model: source → routes → destinations.

The mental model

LeadRails is a router for inbound lead events.

  • Source — where leads come from. A web form, a CRM webhook, a third-party lead vendor. Each source has a signing secret your developer wires into the upstream system.
  • Destination — where leads go. Slack, Salesforce, your CRM via webhook, n8n, Zapier, Make. Each has an adapter type + config.
  • Route — connects a source to one destination. Optionally maps fields. Fan out by creating multiple routes from the same source — they fire in parallel.

Step 1: Create your first source (~3 min)

Open Sources in the admin and click New Source. Name it (e.g. "Acme Pricing Form") and pick a source type (web-form for HTML forms; webhook for arbitrary upstreams).

On the source detail page, open the Settings tab and click Issue signing key. Copy the secret immediately — it's shown only once. Treat it like a password.

The page also shows your source ID (starts with src_) — you'll need this when posting leads.

Step 2: Add at least one destination (~3 min)

DestinationsNew Destination. Pick an adapter:

  • Generic Webhook — POSTs the lead JSON to any URL. Easiest start.
  • Slack Webhook — formatted Slack message.
  • n8n / Zapier / Make Webhook — POSTs to your iPaaS trigger. Lets you fan out through 400+ / 6000+ / 1500+ integrations.
  • GoHighLevel — creates a contact in a GHL pipeline stage.
  • Housecall Pro — creates a customer in HCP.

Step 3: Create a route (~2 min)

RoutesNew Route. Pick the source, destination, and a name like "Acme Form → Slack".

Create one route per destination for fan-out — they fire in parallel.

Step 4: Post your first lead (~5 min)

Intake endpoint: https://intake.leadrails.dev/v1/lead-events. Each request needs HMAC-SHA-256 signing using your source's signing secret.

Minimum body:

{
  "schema_version": "lead_event.v1",
  "event_type": "lead.submitted",
  "source": { "source_system": "web-form" },
  "lead": {
    "email": "lead@example.com",
    "full_name": "Jane Doe",
    "phone": "555-0100"
  },
  "submitted_at": "2026-05-17T12:00:00Z"
}

See docs/customer-onboarding/hmac-signing-guide.md in the repo for Node / Python / PHP code samples.

Verifying outbound signatures (Generic Webhook)

LeadRails security diagram showing HMAC signature verification — the same primitive protects both inbound lead requests to LeadRails and outbound deliveries from LeadRails to your receiver.
Illustrative HMAC signature verification flow.

When you wire a Generic Webhook destination to your own endpoint, you should verify each delivered request actually came from LeadRails. Open the destination, expand Advanced, and create a Signing secret. Copy the value once and configure your receiver to verify it.

Each delivered request carries:

  • X-LeadRails-Timestamp — ISO-8601 UTC, signing-time.
  • X-LeadRails-Signature: v1=<base64> — the HMAC.
  • X-LeadRails-Signature-Key-Id — which secret signed it (handy during rotation).

Verification (Node):

import { createHash, createHmac, timingSafeEqual } from "node:crypto";

function verifyLeadRailsSignature(rawBody, headers, secret) {
  const ts = headers["x-leadrails-timestamp"];
  const sig = headers["x-leadrails-signature"]; // "v1=<b64>"
  if (!ts || !sig?.startsWith("v1=")) return false;

  // Reject anything older than 5 minutes — defence-in-depth against replay.
  if (Math.abs(Date.now() - Date.parse(ts)) > 5 * 60 * 1000) return false;

  const bodyHash = createHash("sha256").update(rawBody).digest("hex");
  const expected = createHmac("sha256", secret)
    .update(`${ts}.${bodyHash}`)
    .digest("base64");

  const a = Buffer.from(sig.slice(3), "base64");
  const b = Buffer.from(expected, "base64");
  return a.length === b.length && timingSafeEqual(a, b);
}

Multiple active secrets are supported for zero-downtime rotation — create a new one, deploy it to your receiver (accepting either), then revoke the old one. The worker always signs with the most recently created active secret.

Step 5 (optional): Per-route field mapping

Each adapter has a default payload shape. To customize, open the route's Mapping page (icon in the routes table). Pick source fields, map them to destination fields, use Mustache templates like {{lead.email}}, set defaults, and use the live preview to verify.

Troubleshooting

401 / invalid_signature
HMAC computation off. Verify you signed the exact JSON body bytes with the right secret + timestamp.
404 source_not_found
Wrong X-LR-Source-Id header. Check the source detail page.
200 but destination doesn't receive it
Check Jobs. Filter by failed status. Open a failed job — the error explains why.

Get started — create your first source →