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.

There's no "Verify connection" button — here's why

Most destinations have a Verify connection check that confirms your credentials without sending a real lead. Slack, for example, answers a LeadRails ping with a harmless error that proves the webhook is valid.

Generic Webhook can't do that. Your URL is an endpoint we know nothing about. The only request we could send is the same POST we send at delivery time. That would run your real receiver: create a lead, page someone, maybe charge a card. There's no safe "just checking" request to make, so we don't pretend to have one.

Instead, confirm the connection by sending a real test event and watching the result (next section). LeadRails still SSRF-guards the URL at create time, so a private or non-HTTPS address is rejected before you ever save it.

Test it

  1. Sources → your source → Settings → ensure a route exists from this source to the Generic Webhook destination
  2. Click Send test event on the source
  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. Set PUT or PATCH in the destination's HTTP method field
  • Content-Type: application/json
  • We add X-Idempotency-Key (the delivery's job id) so your receiver can dedupe retries of the same delivery

Custom headers

Need to send an Authorization header or an API token your receiver expects? Set a headers object on the destination config. It's a flat map of header name to value, merged on top of the Content-Type we send.

Custom headers aren't in the destination form yet. Set them through the API (POST / PATCH /v1/destinations) with a config body like:

{
  "url": "https://hooks.example.com/lead",
  "headers": { "Authorization": "Bearer your-token" }
}

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
  • Timeout: 10 seconds by default. Set timeout_ms up to 30000. A timed-out request is a transient failure and gets retried

Your URL stays private

Your webhook URL is your credential. If it carries a token in the path or query string, treat it like a password. LeadRails only ever records the host of your URL in job records, logs, and the audit trail. The path and query never appear there, and they're never echoed back in an error message. A leaked log can't be used to POST to your endpoint.

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