LeadRails
Docs navigation

Generic Webhook

POST every lead event as JSON to any HTTPS endpoint you control. Optionally sign the request body with HMAC-SHA-256 so your receiver can verify authenticity.

What this is

Generic Webhook is LeadRails's escape hatch. Point it at any URL on any host and we'll POST the lead-event JSON to it. Use this when no curated adapter matches your destination — internal APIs, custom CRMs, n8n self-hosted, anything that speaks JSON-over-HTTP.

In LeadRails

  1. Sign in → Destinations → New
  2. Adapter type: Generic Webhook
  3. URL: your receiver's full URL (must be HTTPS; we SSRF-guard against private IPs at create AND delivery time)
  4. Click Create

Signing requests so your receiver can trust them

Generic Webhook is the only adapter that supports outbound HMAC signing. Your receiver verifies each request by recomputing the signature with a shared secret. Without this, anyone could POST to your URL — signing makes that infeasible.

Generate a signing secret

  1. Open the destination you just created → click Edit → expand Advanced
  2. Scroll to Signing secrets → click New signing secret
  3. Give it a label (e.g. prod-2026-q2), click Generate secret
  4. Copy the secret IMMEDIATELY — it's shown once and then hashed; you can never retrieve it again

How to verify on the receiver side

v1=base64(HMAC-SHA-256(secret, X-LeadRails-Timestamp + "." + sha256_hex(body)))

LeadRails sends X-LeadRails-Signature: v1=<base64> and X-LeadRails-Timestamp: <ISO-8601> headers. Your receiver:

  1. read both headers
  2. compute HMAC-SHA-256(your_secret, timestamp + "." + sha256_hex(raw_request_body))
  3. base64-encode the result
  4. prefix with v1=
  5. compare with the header value using a constant-time string compare

Reject the request if the comparison fails or the timestamp is more than 5 minutes old (to prevent replay).

Multiple active secrets (zero-downtime rotation)

  • You can have multiple active signing secrets at once. LeadRails signs with the most recently created one.
  • During rotation: (1) create a new secret in LeadRails, (2) deploy your receiver to accept either the old OR new secret, (3) revoke the old secret in LeadRails. No downtime.

Test it

  1. Sources → your source → Settings → ensure a route exists from this source to the Generic Webhook destination
  2. Click Send test event in the SourceTestEvent card
  3. Your receiver gets a POST within seconds; the result panel deep-links to the Jobs page so you can confirm the 2xx

Request shape

Default body shape (when no per-route mapping is configured):

{
  "event_id": "evt_xxx",
  "normalized_lead": { "name": "...", "email": "...", "phone": "...", ... },
  "raw_event": { ...the customer's original payload... },
  "sent_at": "ISO-8601 timestamp"
}
  • With per-route mapping: build any custom shape via the Routes → Mapping editor
  • Method: POST by default (configurable to PUT or PATCH)
  • Content-Type: application/json

Reliability

  • Retries: 5 attempts via the in-DB retry counter (NOT Cloudflare Queue's retry — we override that)
  • Backoff schedule: outbox sweeper checks every minute; exponential backoff between attempts
  • Permanent failures (4xx, except 408/429): no retry, alert fires
  • Transient failures (5xx, timeout, network): retried up to the budget; alert if budget exhausted
  • Rate limits (429): respects your Retry-After header if present

Common issues

unsafe_url: private_ip
URL resolved to a private IP at create or delivery time. LeadRails blocks SSRF; expose your service on a public-routable host (e.g. via Cloudflare Tunnel if it's internal).
unsafe_url: must_use_https
HTTP not allowed. Use HTTPS.
Permanent failure but it works in curl
Your receiver returned 4xx. Check the Jobs page for the response body; we capture it for diagnosis.
Signature verification fails
Your receiver is probably hashing the body string instead of the raw bytes. SHA-256 hex of raw_body_bytes, not JSON.stringify(parsed_body).
← All destination guides