Custom Webhook Guide: Get Every New Lead Into Your Own Tools Automatically
Who this guide is for: business owners (and the developers who help them) who run some of their operation on software that isn't a mainstream, off-the-shelf CRM — an in-house database, a spreadsheet the office built over the years, a scheduling tool made for one trade, or an automation platform like Zapier or Make. If a new lead needs to land in that system automatically, a webhook is how you do it.
Use this guide as a straight-through checklist: connect the webhook in Janus, decide which leads it sends, stand up a small piece of code at your web address to receive and verify each message, test it with the built-in test button, and confirm your retry handling before you switch it on for real customers. It's a one-time setup, and once it's running you never touch it again. One thing to check first: the custom webhook is part of the Pro plan, so make sure your Janus account is on Pro before you start — you won't see the setup option on Starter.
How to send new leads straight into your own system (a step-by-step webhook guide)
Short version: On the Janus Pro plan you can point us at a secure web address you control. Every time a new lead comes in, we send that lead's details to your address within seconds, signed so you can prove it came from us. You set it up once in Settings. This guide walks through every step, exactly what we send, how to check it's genuine, and what to do when something goes wrong.
If you just want the feature overview and pricing, see the Custom Pro Webhook page.
What is a webhook, in plain terms?
A webhook is a message your software receives automatically when something happens somewhere else. Instead of your system asking Janus "any new leads?" every few minutes, Janus tells your system the moment a lead arrives, by sending a small package of information to a web address you gave us.
It only goes one way: Janus sends, your system receives. We never read anything back out of your system.
This is the right tool when the software you actually run your business on — an in-house database, a spreadsheet your office built, a niche app for your trade — isn't in a standard "connect to [popular tool]" menu. More on when to use it on the webhook feature page.
What you need before you start
A Janus Pro plan. The custom webhook is included in Pro at no extra charge. It isn't available on Starter.
A web address (URL) your system listens on. It must:
start with https:// (a plain http:// address is rejected),
have a valid security certificate,
be reachable from the public internet (not localhost, not an address only your office network can see),
accept a POST request.
A way to check a signature. Every common programming language can do this with a built-in function (it's called HMAC-SHA256). You do not need any special library from us.
Somewhere safe to store a secret key. Treat it like a password — an environment variable or a secrets manager, never pasted into code you commit.
The ability to reply quickly. Your address needs to answer within 10 seconds. If your processing takes longer, accept the request first, reply, then do the slow work in the background.
If you don't have a developer in-house, this is a small job for any freelance web developer — send them this page and the developer reference.
Step 1: Connect the webhook in Janus
Sign in to Janus and open Settings.
Find the Custom webhook (open API) card. If you're on Pro, you'll see a place to enter a URL. If you're on Starter, you'll see an upgrade prompt instead.
Paste your https:// address and select Connect webhook.
Janus shows you a signing secret — a long string of letters and numbers. Copy it now and store it somewhere safe. We show it once and cannot show it again. If you lose it, you can roll a new one (Step 6), but you can't look the old one up.
You get one webhook per Janus account. Connecting a webhook replaces any other CRM connection on the account, and vice-versa.
Step 2: Choose which leads get sent
In the same Settings card there's a Send setting with three choices:
SettingWhat it doesEvery lead(default)We send every lead the moment it's captured through your intake form.Hot & warm onlySame, but we skip leads that scored "cold."Won deals onlyWe send nothing on capture. Instead, we send a message when you move a deal to the Won stage in your pipeline.
You can change this any time. If you pick "Won deals only," the message you receive is a deal.won message (details below) rather than a lead.created one.
Step 3: Build the receiver at your address
Your address needs to do four things, in order, every time it gets a request:
Read the raw body of the request as text, before your framework turns it into an object. The signature is calculated on the exact bytes we sent; if your code re-formats the JSON first, the check will fail.
Check the timestamp. Read the X-Janus-Timestamp header. If it's more than 300 seconds (5 minutes) away from your server's current time, reject the request. This stops someone replaying an old message.
Check the signature. Recompute it (next section) and compare. If it doesn't match, reject the request with a 401.
Only then, parse the JSON and act on it.
Step 4: Verify the signature
We send these headers with every request:
HeaderWhat it isContent-TypeAlways application/json.X-Janus-TimestampThe time we sent it, in Unix seconds.X-Janus-SignatureProof it's from us: the hex HMAC-SHA256 of the text "{X-Janus-Timestamp}.{raw body}", using your signing secret as the key.X-Janus-Delivery-IdA unique ID for this delivery. It stays the same across retries of the same lead.X-Janus-Signature-PreviousOnly present for 24 hours after you roll your secret (Step 6). Same calculation with your old secret. Accept the request if either signature matches.
Do not try to identify our requests by their IP address — we don't publish a fixed set of sending IPs. The signature is how you know a request is genuine.
Node.js
import crypto from "node:crypto";
function verify(rawBody, headers, secret) {
const ts = headers["x-janus-timestamp"];
const sig = headers["x-janus-signature"];
if (!ts || !sig) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
try {
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
} catch {
return false;
}
}Python
import hmac, hashlib, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
ts = headers.get("X-Janus-Timestamp")
sig = headers.get("X-Janus-Signature")
if not ts or not sig:
return False
if abs(time.time() - int(ts)) > 300:
return False
expected = hmac.new(
secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig)PHP
<?php
function verify(string $rawBody, array $headers, string $secret): bool {
$ts = $headers['X-Janus-Timestamp'] ?? null;
$sig = $headers['X-Janus-Signature'] ?? null;
if (!$ts || !$sig) return false;
if (abs(time() - (int)$ts) > 300) return false;
$expected = hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
return hash_equals($expected, $sig);
}Full reference and more examples: myjanus.pro/docs/webhook.
Step 5: Know exactly what we send
Every request has the same outer shape:
{
"schemaVersion": 1,
"event": "lead.created",
"deliveryId": "b1f2c3…",
"attempt": 1,
"sentAt": "2026-09-02T14:05:11.000Z",
"data": { }
}schemaVersion — bumps only if we make a breaking change to data. We can add new fields without bumping it, so don't reject a request just because it has a field you don't recognize.
event — "lead.created", "deal.won", or "test".
deliveryId — unique per delivery, same across retries. Store it and ignore duplicates — a delivery can arrive twice if your reply got lost on the way back.
attempt — 1 on the first try, higher on retries.
data for a lead.created event
{
"leadId": "…",
"fullName": "Jordan Rivera",
"email": "[email protected]",
"phone": "+18605551234",
"propertyType": "residential",
"servicesInterested": ["patios", "retaining_walls"],
"projectDetails": "…",
"timeline": "asap",
"createdAt": "2026-09-02T14:05:10.000Z"
}phone, propertyType, projectDetails, and timeline can be null. servicesInterested is always an array (sometimes empty).
data for a deal.won event
{
"dealId": "…",
"contactId": "…",
"contactName": "Jordan Rivera",
"email": "[email protected]",
"phone": "+18605551234",
"title": "Back patio + retaining wall",
"stage": "won",
"estimatedValue": 14200,
"servicesInterested": ["patios"],
"score": 72,
"scoreLabel": "hot",
"wonAt": "2026-09-04T18:20:00.000Z"
}data for a test event
A test event has the same lead.created shape with obviously fake values ("fullName": "Test Lead", "email": "[email protected]", and so on). You'll see these when you use the test button in Step 7.
Step 6: Roll your signing secret (when you need to)
If your secret is ever exposed, or on a regular schedule, use Roll signing secret in Settings:
You get a new secret, shown once.
For 24 hours, your old secret still works. During that window every request carries both X-Janus-Signature (new secret) and X-Janus-Signature-Previous (old secret).
Update your system to the new secret within 24 hours. While you're mid-change, accept a request if either header verifies.
After 24 hours we stop sending the old signature.
This means you can rotate the secret with zero downtime and no dropped leads.
Step 7: Test it before you rely on it
In Settings → Custom webhook:
Send test event fires a sample lead at your address right now and shows you exactly what your server replied — the status code and any error. Use this to confirm your signature check and your reply are working before a real customer comes through.
Recent deliveries is a live list of the last couple of dozen attempts: what was sent, whether it landed, the status code your server returned, how many attempts it took, and — if it's waiting to retry — when the next attempt is.
To test against code running on your own laptop, put a secure tunnel (such as ngrok or cloudflared) in front of it so it has a real https:// address, and use that as your webhook URL while you build.
Step 8: Handle the reply and the retries correctly
What we expect back: a 2xx status code within 10 seconds. Anything else — or no reply in time — counts as a failed delivery, even if your system eventually processes the request.
What happens on a failure:
Your responseWhat Janus does2xxMarked delivered. Done.5xx, 408, 429, a connection error, or a timeoutRetried. Immediate attempt, then again after 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours — six attempts over about nine hours.Any other 4xx (e.g. 400, 404, 422)Not retried. We treat it as "this request will never be accepted."401, 403, or 410Not retried, and the webhook is switched off. You get an email and need to reconnect it in Settings.
If all six attempts fail, we mark the delivery "gave up" and email the account owner. The lead is always safe inside Janus — only the copy to your system failed, and you can still see it in your pipeline.
Because retries exist, two rules matter for your code:
Be idempotent. Use deliveryId to make sure processing the same delivery twice has no extra effect.
Don't assume order. A retry of an older lead can land after a newer lead. If order matters to you, sort by the createdAt / wonAt inside data, not by the order requests arrive.
Common mistakes and how to avoid them
Parsing the body before checking the signature. Read the raw text first. Frameworks that auto-parse JSON will change the bytes and break the check — look for a "raw body" or "text body" option.
Comparing signatures with ==. Use a constant-time comparison (timingSafeEqual, hmac.compare_digest, hash_equals).
Doing slow work before replying. Acknowledge with a 2xx, then process. A slow reply is a failed delivery.
Blocking us by IP. We don't have fixed sending IPs. Verify the signature instead.
Relying on a redirect. Give us the final URL. Don't point us at an address that 301s somewhere else.
Storing the secret in code. Put it in an environment variable or secrets manager.
Ignoring duplicates. Always dedupe on deliveryId.
Frequently asked questions
Do I need to be on the Pro plan?
Yes. The custom webhook is part of Pro and isn't available on Starter. Pricing is on the Custom Pro Webhook page.
Can I connect more than one webhook?
No — one per Janus account. If you need to fan a lead out to several systems, receive it at one address and forward it from there.
How fast does the lead arrive?
The first attempt goes out within seconds of the lead being captured.
What if my server is down for a few hours?
Janus keeps retrying for about nine hours (six attempts). If it still can't get through, the account owner gets an email and the lead stays safe in Janus.
Can I use this to send leads to Zapier, Make, or n8n?
Yes. Point the webhook at the "catch hook" URL those tools give you, verify the signature (or, on a trusted private URL, at least check the timestamp), and build your automation from there.
Does Janus read anything from my system?
No. It's one-directional: Janus sends, your system receives.
What happens if I disconnect and reconnect?
Your delivery history is kept. You'll get a new signing secret on reconnect.
Is there a sandbox or test mode?
Use the Send test event button and a tunnel to your local machine. Test events are clearly marked with event: "test" and fake data.
Ready to set it up?
The feature overview, plan details, and pricing are on the Custom Pro Webhook page. The full technical reference — every field, header, and the retry schedule — is at myjanus.pro/docs/webhook. If you get stuck, email [email protected].
Last updated: September 2026.