Webhook Setup
Create and configure webhook endpoints to receive HelloJohn events.
Webhook Setup
Webhooks let HelloJohn push real-time notifications to your server when events occur — user created, session revoked, MFA enrolled, and more.
Step 1: Create an Endpoint in Your App
Your server needs a public HTTPS endpoint that accepts POST requests:
import express from "express";
import crypto from "crypto";
const app = express();
// Use raw body for signature verification
app.use("/webhooks/hellojohn", express.raw({ type: "application/json" }));
app.post("/webhooks/hellojohn", (req, res) => {
const sig = req.headers["hellojohn-signature"] as string;
const payload = req.body.toString();
// Verify signature (see below)
if (!verifySignature(payload, sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(payload);
console.log("Received event:", event.type);
// Always respond quickly — process async if needed
res.sendStatus(200);
// Process the event after responding
handleEvent(event).catch(console.error);
});
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}Requirements for your endpoint:
- Must be HTTPS (TLS)
- Must respond within 30 seconds
- Must return
2xxstatus to acknowledge delivery - Any other status code is treated as failure and triggers retries
Step 2: Register the Webhook
Via Dashboard
Go to Tenant Settings → Webhooks → Add Endpoint.
Via API
curl -X POST "https://api.hellojohn.dev/v1/webhooks" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.your-app.com/webhooks/hellojohn",
"events": ["user.created", "user.deleted", "session.created"],
"description": "User lifecycle events"
}'Response: 201 Created
{
"id": "wh_01HABCDEF777001",
"url": "https://api.your-app.com/webhooks/hellojohn",
"events": ["user.created", "user.deleted", "session.created"],
"signing_secret": "whsec_abc123defghijklmnop...",
"created_at": "2024-01-15T10:00:00Z"
}Save the signing_secret — it's shown once and used to verify webhook payloads.
Step 3: Configure Your Environment
# .env
WEBHOOK_SECRET=whsec_abc123defghijklmnop...Step 4: Send a Test Event
Verify your endpoint receives events:
curl -X POST "https://api.hellojohn.dev/v1/webhooks/wh_01HABCDEF777001/test" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"event": "user.created"}'Check your server logs to confirm receipt.
Subscribing to Events
Use ["*"] to subscribe to all events:
{ "events": ["*"] }Or specify individual events:
{
"events": [
"user.created",
"user.deleted",
"session.created",
"mfa.enrolled",
"org.member.added"
]
}See the full Events Reference.
Local Development
For local development, use a tunnel to expose your local server:
# Using ngrok
ngrok http 3000
# Using Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000Register the tunnel URL as a webhook endpoint while developing.
Multiple Endpoints
You can register multiple endpoints — useful for different services subscribing to different events:
# Analytics service
POST /v1/webhooks → url: analytics.example.com, events: ["user.created", "session.created"]
# Billing service
POST /v1/webhooks → url: billing.example.com, events: ["user.deleted"]
# Audit service
POST /v1/webhooks → url: audit.example.com, events: ["*"]Disabling a Webhook
Temporarily disable without deleting:
curl -X PATCH "https://api.hellojohn.dev/v1/webhooks/wh_01HABCDEF777001" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'