Webhooks API
REST API endpoints for creating and managing webhook endpoints and event subscriptions.
Webhooks API
Webhooks allow HelloJohn to push real-time event notifications to your server. Configure endpoints to receive events like user creation, sign-in, or MFA enrollment.
Webhook Object
{
"id": "wh_01HABCDEF777000",
"url": "https://api.example.com/webhooks/hellojohn",
"description": "User lifecycle events",
"events": ["user.created", "user.deleted", "session.created"],
"status": "active",
"signing_secret": "whsec_...",
"created_at": "2024-01-10T09:00:00Z",
"last_triggered_at": "2024-01-20T08:15:00Z"
}Available Events
| Event | Description |
|---|---|
user.created | A new user account was created |
user.updated | User profile was updated |
user.deleted | User was deleted |
user.disabled | User was disabled |
user.password_changed | User changed their password |
session.created | A new session was created (sign-in) |
session.revoked | A session was revoked (sign-out or forced) |
mfa.enrolled | User enrolled a new MFA factor |
mfa.removed | User removed an MFA factor |
mfa.verified | User completed an MFA challenge |
org.created | Organization was created |
org.deleted | Organization was deleted |
org.member.added | Member was added to organization |
org.member.removed | Member was removed from organization |
org.invitation.sent | An invitation was sent |
org.invitation.accepted | An invitation was accepted |
oauth.connected | User connected an OAuth provider |
oauth.disconnected | User disconnected an OAuth provider |
GET /v1/webhooks
List all configured webhook endpoints.
curl "https://api.hellojohn.dev/v1/webhooks" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321"Response:
{
"data": [
{
"id": "wh_01HABCDEF777000",
"url": "https://api.example.com/webhooks/hellojohn",
"events": ["user.created", "session.created"],
"status": "active",
"created_at": "2024-01-10T09:00:00Z"
}
]
}POST /v1/webhooks
Create a new webhook endpoint.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | ✅ | HTTPS URL to receive events |
events | array | ✅ | List of events to subscribe to. Use ["*"] for all events |
description | string | — | Human-readable label |
enabled | boolean | — | Default: true |
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.example.com/webhooks/hellojohn",
"events": ["user.created", "user.deleted", "session.created"],
"description": "User lifecycle events"
}'Response: 201 Created
{
"id": "wh_01HABCDEF777001",
"url": "https://api.example.com/webhooks/hellojohn",
"events": ["user.created", "user.deleted", "session.created"],
"status": "active",
"signing_secret": "whsec_abc123...",
"created_at": "2024-01-15T10:00:00Z"
}Save the
signing_secret— it is shown only once and is used to verify webhook signatures.
GET /v1/webhooks/:id
Get details for a specific webhook.
PATCH /v1/webhooks/:id
Update a webhook endpoint.
Body (all optional):
| Field | Type | Description |
|---|---|---|
url | string | New endpoint URL |
events | array | New events list (replaces existing) |
description | string | New label |
enabled | boolean | Enable or disable the endpoint |
DELETE /v1/webhooks/:id
Delete a webhook endpoint.
Response: 204 No Content
POST /v1/webhooks/:id/test
Send a test event to the endpoint.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
event | string | — | Event type to simulate (default: user.created) |
curl -X POST "https://api.hellojohn.dev/v1/webhooks/wh_01HABCDEF777000/test" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"event": "user.created"}'Response:
{
"delivery_id": "del_01HABCDEF888001",
"status": "delivered",
"http_status": 200,
"duration_ms": 142
}GET /v1/webhooks/:id/deliveries
List recent delivery attempts for a webhook.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | delivered, failed, pending |
limit | integer | Default 20, max 100 |
cursor | string | Pagination cursor |
Response:
{
"data": [
{
"id": "del_01HABCDEF888001",
"webhook_id": "wh_01HABCDEF777000",
"event": "user.created",
"status": "delivered",
"http_status": 200,
"duration_ms": 142,
"attempts": 1,
"created_at": "2024-01-20T08:15:00Z"
}
]
}POST /v1/webhooks/deliveries/:id/retry
Retry a failed delivery.
curl -X POST "https://api.hellojohn.dev/v1/webhooks/deliveries/del_01HABCDEF888001/retry" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321"Response: 202 Accepted
Webhook Payload Format
All webhook payloads follow the same structure:
{
"id": "evt_01HABCDEF999001",
"type": "user.created",
"tenant_id": "tnt_01HABCDEF654321",
"created_at": "2024-01-20T08:15:00Z",
"data": {
"id": "usr_01HABCDEF123456",
"email": "alice@example.com",
"name": "Alice Smith",
"created_at": "2024-01-20T08:15:00Z"
}
}Verifying Signatures
Every webhook includes a HelloJohn-Signature header. Verify it to ensure the request came from HelloJohn:
import crypto from "crypto";
function verifyWebhook(
payload: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
// Express handler
app.post("/webhooks/hellojohn", (req, res) => {
const sig = req.headers["hellojohn-signature"] as string;
const raw = req.rawBody; // raw string body
if (!verifyWebhook(raw, sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(raw);
// handle event...
res.sendStatus(200);
});