Synchronisation des utilisateurs avec les webhooks
Lorsque vous supprimez ou bannissez un utilisateur dans Authon, la base de données de votre application ne le sait pas automatiquement. Les webhooks résolvent ce problème en livrant des événements en temps réel à votre backend.
Le problème
Sans webhooks, votre application n'a aucun moyen de savoir quand l'état utilisateur d'Authon change. Cela entraîne des données obsolètes — des utilisateurs supprimés qui apparaissent encore, des utilisateurs bannis qui accèdent toujours aux fonctionnalités.
La solution
Abonnez-vous aux événements user.*. Lorsqu'ils se déclenchent, mettez à jour votre base de données pour correspondre à l'état d'Authon.
Exemple Express + Prisma
Un gestionnaire webhook complet qui maintient une table d'utilisateurs gérée par Prisma synchronisée avec Authon.
import express from "express";
import { createHmac, timingSafeEqual } from "crypto";
import { PrismaClient } from "@prisma/client";
const app = express();
const prisma = new PrismaClient();
function verifyWebhook(
rawBody: Buffer,
signature: string,
timestamp: string,
secret: string,
): boolean {
const payload = `${timestamp}.${rawBody.toString()}`;
const expected = createHmac("sha256", secret).update(payload).digest("hex");
const actual = signature.replace("v1=", "");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(actual, "hex"));
}
app.post(
"/webhooks/authon",
express.raw({ type: "application/json" }),
async (req, res) => {
const sig = req.headers["x-authon-signature"] as string;
const ts = req.headers["x-authon-timestamp"] as string; // ISO 8601
if (!verifyWebhook(req.body, sig, ts, process.env.AUTHON_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const { event, data } = JSON.parse(req.body.toString());
switch (event) {
case "user.created":
case "user.updated":
await prisma.user.upsert({
where: { authonId: data.user.id },
create: {
authonId: data.user.id,
email: data.user.email,
name: data.user.displayName,
avatarUrl: data.user.avatarUrl,
},
update: {
email: data.user.email,
name: data.user.displayName,
avatarUrl: data.user.avatarUrl,
},
});
break;
case "user.deleted":
await prisma.user.update({
where: { authonId: data.user.id },
data: { deletedAt: new Date() }, // soft-delete
}).catch(() => {});
break;
case "user.banned":
await prisma.user.update({
where: { authonId: data.user.id },
data: { suspended: true, suspendedAt: new Date() },
});
break;
case "user.unbanned":
await prisma.user.update({
where: { authonId: data.user.id },
data: { suspended: false, suspendedAt: null },
});
break;
}
res.json({ received: true });
}
);Exemple Next.js App Router
Une route API Next.js qui gère les événements de synchronisation des utilisateurs en utilisant l'App Router.
import { createHmac, timingSafeEqual } from "crypto";
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
export async function POST(req: NextRequest) {
const rawBody = await req.text();
const signature = req.headers.get("x-authon-signature")!;
const timestamp = req.headers.get("x-authon-timestamp")!; // ISO 8601
const payload = `${timestamp}.${rawBody}`;
const expected = createHmac("sha256", process.env.AUTHON_WEBHOOK_SECRET!)
.update(payload)
.digest("hex");
const actual = signature.replace("v1=", "");
if (!timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(actual, "hex"))) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const { event, data } = JSON.parse(rawBody);
if (event === "user.created" || event === "user.updated") {
await db.user.upsert({
where: { authonId: data.user.id },
create: {
authonId: data.user.id,
email: data.user.email,
name: data.user.displayName,
},
update: {
email: data.user.email,
name: data.user.displayName,
},
});
} else if (event === "user.deleted") {
await db.user.update({
where: { authonId: data.user.id },
data: { deletedAt: new Date() }, // soft-delete
}).catch(() => {});
}
return NextResponse.json({ received: true });
}Cas limites
Synchronisation inverse (Application → Authon)
Les webhooks gèrent la direction Authon → Application. Pour la direction inverse — créer ou mettre à jour des utilisateurs dans Authon depuis votre application — utilisez l'API backend avec votre clé secrète.
import { AuthonBackend } from "@authon/node";
const authon = new AuthonBackend(process.env.AUTHON_SECRET_KEY!);
// Create a user in Authon from your app (e.g. after admin invite)
export async function createAuthonUser(email: string, name: string) {
return authon.users.create({
email,
displayName: name,
emailVerified: true,
});
}
// Update user metadata in Authon when your app data changes
export async function syncMetadataToAuthon(
authonId: string,
publicMetadata: Record<string, unknown>,
) {
return authon.users.update(authonId, { publicMetadata });
}
// Bulk import existing users from your app into Authon
export async function bulkImportToAuthon(
users: Array<{ email: string; name: string }>,
) {
for (const user of users) {
await authon.users.create({
email: user.email,
displayName: user.name,
emailVerified: true,
});
}
}Toutes les opérations de gestion des utilisateurs sont disponibles via le point de terminaison REST API POST /v1/backend/users.