Webhooks

Webhooks

Receive real-time HTTP notifications when authentication events happen in your project. Sync users to your database, send welcome emails, or trigger workflows whenever users sign up, sign in, or get banned.

What are Webhooks?

A webhook is an HTTP callback your server registers to receive real-time event notifications. Instead of polling Authon to check if a user was deleted or banned, Authon proactively pushes a POST request to your URL the moment the event happens.

text
User deleted in Authon dashboard
  → Authon sends POST /webhooks/authon
  → Your server receives the event and removes the user from your DB

How Webhooks Work

When an event occurs in your Authon project, the API sends a POST request to your registered endpoint with a JSON payload. Each request includes an X-Authon-Signature header with a v1= prefix — an HMAC-SHA256 signature computed over timestamp.body using your webhook signing secret. Always verify this signature before processing the event.

1
An event occurs in Authon (e.g., a user signs up)
2
Authon signs the payload with HMAC-SHA256 using your webhook secret
3
Authon sends a POST request to your endpoint URL
4
Your server verifies the signature and processes the event
5
Your server responds with 2xx to acknowledge receipt

Setting Up a Webhook

  1. 1
    Navigate to Dashboard → Webhooks → Add Webhook
  2. 2
    Enter your endpoint URL — must be publicly accessible via HTTPS
  3. 3
    Select the events you want to receive
  4. 4
    Copy the signing secret — shown only once, store it securely
  5. 5
    Set AUTHON_WEBHOOK_SECRET in your server environment

Event Types

EventTrigger
user.createdA new user completes registration
user.updatedA user's profile is updated
user.deletedA user account is permanently deleted
user.signinA user signs in (email or OAuth)
user.signoutA user signs out
user.bannedA user is banned by an admin
user.unbannedA banned user is restored
session.createdA new session is created (sign in, token refresh)
session.revokedA session is revoked (sign out, admin revoke)

Payload Format

Every webhook payload is a JSON object with a consistent top-level structure. The data field contains event-specific details.

json
{
  "event": "user.created",
  "data": {
    "user": {
      "id": "usr_abc123",
      "email": "user@example.com",
      "displayName": "Jane Doe",
      "emailVerified": false,
      "isBanned": false,
      "publicMetadata": null,
      "createdAt": "2026-01-15T10:30:00.000Z",
      "updatedAt": "2026-01-15T10:30:00.000Z"
    }
  },
  "timestamp": "2026-01-15T10:30:00.000Z"
}

Forsession.*events, the datafield also includes a sessionobject with id, ipAddress, and userAgent.

Signature Verification

Each webhook request contains an X-Authon-Signature header with the format v1=<hex_digest>. The signature is computed as HMAC-SHA256 over timestamp.rawBody using your webhook secret. Compare the result with timing-safe equality.

HeaderFormatDescription
X-Authon-Signaturev1={hmac_sha256}HMAC-SHA256 signature for verifying the request origin
X-Authon-TimestampISO 8601ISO 8601 timestamp of when the event was sent
X-Authon-Eventuser.createdThe event type (e.g. user.created, session.revoked)
Always use the raw body buffer for verification, not a parsed JSON string. Express's json() middleware re-serializes the body which may alter whitespace and break the signature.

Node.js Example

routes/webhooks.ts
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";

const router = express.Router();

function verifySignature(
  rawBody: Buffer,
  signature: string,
  secret: string,
  timestamp: string,
): boolean {
  const payload = `${timestamp}.${rawBody.toString()}`;
  const expected = createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  const actual = signature.replace("v1=", "");

  const expectedBuf = Buffer.from(expected, "hex");
  const actualBuf = Buffer.from(actual, "hex");

  return (
    expectedBuf.length === actualBuf.length &&
    timingSafeEqual(expectedBuf, actualBuf)
  );
}

router.post(
  "/webhooks/authon",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-authon-signature"] as string;
    const timestamp = req.headers["x-authon-timestamp"] as string; // ISO 8601
    const eventType = req.headers["x-authon-event"] as string;
    const webhookSecret = process.env.AUTHON_WEBHOOK_SECRET!;

    if (!verifySignature(req.body, signature, webhookSecret, timestamp)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const { event, data } = JSON.parse(req.body.toString());

    switch (eventType) {
      case "user.created":
        // Sync new user to your database
        break;
      case "user.deleted":
        // Remove user from your database
        break;
      case "user.updated":
        // Update user data
        break;
      case "user.banned":
        // Revoke app-level access
        break;
    }

    res.status(200).json({ received: true });
  }
);

export default router;

Using the SDK

The@authon/node SDK provides a built-in verify helper that handles timing-safe comparison for you:

ts
import { AuthonBackend } from "@authon/node";

const authon = new AuthonBackend(process.env.AUTHON_SECRET_KEY!);

router.post(
  "/webhooks/authon",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-authon-signature"] as string;
    const timestamp = req.headers["x-authon-timestamp"] as string;

    let event: Record<string, unknown>;
    try {
      event = authon.webhooks.verify(
        req.body,
        signature,
        timestamp,
        process.env.AUTHON_WEBHOOK_SECRET!,
      );
    } catch {
      return res.status(401).json({ error: "Invalid signature" });
    }

    console.log("Verified event:", event.event);
    res.json({ received: true });
  }
);

Retry Policy

If your endpoint returns a non-2xx status code or does not respond within 10 seconds, Authon retries the delivery with exponential backoff. Maximum 3 attempts total.

AttemptDelayCumulative Time
Initial0s
1st retry1 second~1s
2nd retry2 seconds~3s

Use Cases

Sync users to your database
Keep your app's user table in sync with Authon. Listen to user.created, user.updated, and user.deleted to mirror changes automatically.
Send welcome emails
Trigger a welcome email or onboarding sequence when user.created fires. Use the user's email and display name from the event payload.
Enforce bans in real time
When user.banned fires, immediately revoke app-level access tokens or cache entries so the user cannot continue using your service.

Best Practices

Always verify the signature
Never skip signature verification. Anyone can POST to your endpoint — verification proves the request came from Authon.
Use raw body parsing
Signature verification requires the exact raw bytes of the request body. Avoid JSON middleware on webhook routes.
Respond quickly, process async
Return 200 immediately and process the event asynchronously. Authon will retry if your endpoint takes longer than 10s.
Make handlers idempotent
You may receive the same event more than once due to retries. Use the event timestamp to deduplicate — store processed events in your DB.
Rotate secrets periodically
Generate a new signing secret from the Dashboard every 90 days. Update AUTHON_WEBHOOK_SECRET in your deployment without downtime.
Authon — Universal Authentication Platform