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
- Sign in → Destinations → New
- Adapter type:
Generic Webhook - URL: your receiver's full URL (must be HTTPS; we SSRF-guard against private IPs at create AND delivery time)
- 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
- Open the destination you just created → click Edit → expand Advanced
- Scroll to Signing secrets → click New signing secret
- Give it a label (e.g.
prod-2026-q2), click Generate secret - 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:
- read both headers
- compute
HMAC-SHA-256(your_secret, timestamp + "." + sha256_hex(raw_request_body)) - base64-encode the result
- prefix with
v1= - 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
- Sources → your source → Settings → ensure a route exists from this source to the Generic Webhook destination
- Click Send test event on the source
- 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:
POSTby default. SetPUTorPATCHin 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-Afterheader if present - Timeout: 10 seconds by default. Set
timeout_msup 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, notJSON.stringify(parsed_body).