HelloJohn / docs
API Reference

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

EventDescription
user.createdA new user account was created
user.updatedUser profile was updated
user.deletedUser was deleted
user.disabledUser was disabled
user.password_changedUser changed their password
session.createdA new session was created (sign-in)
session.revokedA session was revoked (sign-out or forced)
mfa.enrolledUser enrolled a new MFA factor
mfa.removedUser removed an MFA factor
mfa.verifiedUser completed an MFA challenge
org.createdOrganization was created
org.deletedOrganization was deleted
org.member.addedMember was added to organization
org.member.removedMember was removed from organization
org.invitation.sentAn invitation was sent
org.invitation.acceptedAn invitation was accepted
oauth.connectedUser connected an OAuth provider
oauth.disconnectedUser 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:

FieldTypeRequiredDescription
urlstringHTTPS URL to receive events
eventsarrayList of events to subscribe to. Use ["*"] for all events
descriptionstringHuman-readable label
enabledbooleanDefault: 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):

FieldTypeDescription
urlstringNew endpoint URL
eventsarrayNew events list (replaces existing)
descriptionstringNew label
enabledbooleanEnable 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:

FieldTypeRequiredDescription
eventstringEvent 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:

ParameterTypeDescription
statusstringdelivered, failed, pending
limitintegerDefault 20, max 100
cursorstringPagination 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);
});

On this page