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.
User deleted in Authon dashboard
→ Authon sends POST /webhooks/authon
→ Your server receives the event and removes the user from your DBHow 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.
Setting Up a Webhook
- 1Navigate to Dashboard → Webhooks → Add Webhook
- 2Enter your endpoint URL — must be publicly accessible via HTTPS
- 3Select the events you want to receive
- 4Copy the signing secret — shown only once, store it securely
- 5Set AUTHON_WEBHOOK_SECRET in your server environment
Event Types
| Event | Trigger |
|---|---|
| user.created | A new user completes registration |
| user.updated | A user's profile is updated |
| user.deleted | A user account is permanently deleted |
| user.signin | A user signs in (email or OAuth) |
| user.signout | A user signs out |
| user.banned | A user is banned by an admin |
| user.unbanned | A banned user is restored |
| session.created | A new session is created (sign in, token refresh) |
| session.revoked | A 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.
{
"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.
| Header | Format | Description |
|---|---|---|
| X-Authon-Signature | v1={hmac_sha256} | HMAC-SHA256 signature for verifying the request origin |
| X-Authon-Timestamp | ISO 8601 | ISO 8601 timestamp of when the event was sent |
| X-Authon-Event | user.created | The event type (e.g. user.created, session.revoked) |
json() middleware re-serializes the body which may alter whitespace and break the signature.Node.js Example
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:
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.
| Attempt | Delay | Cumulative Time |
|---|---|---|
| Initial | — | 0s |
| 1st retry | 1 second | ~1s |
| 2nd retry | 2 seconds | ~3s |