On the Pro plan you can point Janus at an HTTPS endpoint you control. Every lead captured through your intake form is POSTed there as a signed JSON envelope — one immediate attempt the moment the lead comes in, then automatic retries if that fails. Push only: Janus never reads from your endpoint.
Connect it in Settings → Custom webhook (open API). You get a signing secret, shown once. There is one webhook per account.
What fires a delivery
Settings has a Send control:
- Every lead (default) — an
event: "lead.created"delivery the moment a lead is captured. - Hot & warm only — same, but cold-scored leads are skipped.
- Won deals only — nothing on capture; an
event: "deal.won"delivery when a deal first moves to the Won stage. Itsdatais the deal (dealId,title,estimatedValue, contact fields,servicesInterested,score,wonAt).
Routing by trade
Every event's data also carries two tags for the business's trade, set from its industry in Janus, so you can route incoming jobs by vertical:
group—home-property,exterior-grounds,auto,personal-services, and so on.industry— the trade within it, for exampleplumbing,hvac,roofing,mobile-mechanic,pet-grooming-boarding.
Both are lowercase, hyphenated, and stable. A business whose industry is "Other" sends industry: "other" and group: null; one with no industry set sends both as null. The tags describe the business, not the individual job.
Request
POST your HTTPS URL
Content-Type: application/jsonX-Janus-Timestamp— Unix seconds when the attempt was made.X-Janus-Signature— hexHMAC-SHA256(secret, "{X-Janus-Timestamp}.{raw body}").X-Janus-Delivery-Id— equals the envelope'sdeliveryId; stable across retries, so dedupe on it.
Payload
Treat data as the lead. The envelope around it is versioned — schemaVersion bumps only on a breaking change to data. New optional fields can appear without a bump, so don't reject on unknown keys.
Verify every request
- Read the raw bytes. Take the request body exactly as it arrived — do not re-serialize it.
- Check the timestamp. Reject if
X-Janus-Timestampis more than 300 seconds from your clock (bounds replay). - Recompute the HMAC. Over
{X-Janus-Timestamp}.{raw body}, and constant-time compare toX-Janus-Signature. - Then parse. Only now parse and trust the payload.
See it in Node, Python or PHP.
Responding
- Return
2xxwithin 10 seconds to acknowledge. Anything else — or no response in time — counts as a failed delivery. 5xx,408,429, a connection error, or a timeout are retried.- Any other
4xxis treated as permanent — Janus does not retry it. 401,403, or410also switch the webhook off — you'll get an email and need to reconnect it from Settings.
Retry schedule
Immediate attempt on capture, then, if it failed retryably: +1m → +5m → +30m → +2h → +6h. Six attempts total over roughly nine hours. After the last one, the delivery is marked gave up and the account owner is emailed — the lead is still safe in Janus, only the webhook copy failed.
Retries reuse the same deliveryId with an incremented attempt. Your endpoint should be idempotent on deliveryId: a delivery can arrive more than once if your 2xx was lost in transit.
Rotating the secret
Settings → Custom webhook has a Roll signing secret button. You get a new secret, shown once. For 24 hours the old secret keeps working: Janus signs each delivery with the new secret in X-Janus-Signature and the old one in X-Janus-Signature-Previous. Accept a request if either header verifies. Deploy the new secret within the window, then you can drop the fallback check.
Testing
Settings → Custom webhook has a Send test event button. It POSTs an envelope with event: "test" and a synthetic dataobject, and shows you exactly what your endpoint returned. Test events show in the same Recent deliveries list as real ones.
Need technical support? Open a ticket in our Help Portal