Framework Integration¶
Complete webhook setup examples for Express, Next.js, and NestJS.
See also: Webhooks Guide | Event Handler Reference
Express.js¶
import express from 'express';
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
const app = express();
// Your event handler
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
}
async onMemberJoined(event) {
console.log(`${event.data.email} joined with roles:`, event.data.tenant_roles);
}
}
// Create webhook router
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!, // Or set AV_HOST env var
handler: new MyEventHandler(),
maxTimestampAge: 300, // 5 min replay protection
keysCacheTtl: 3600000, // 1 hour JWKS cache
});
// IMPORTANT: Use express.raw() for signature verification!
// The body must be the raw buffer, not parsed JSON.
app.post(
'/webhooks/authvital',
express.raw({ type: 'application/json' }),
webhookRouter.expressHandler()
);
app.listen(3000, () => {
console.log('Webhook endpoint ready at http://localhost:3000/webhooks/authvital');
});
Express with Existing JSON Parser¶
If you have express.json() globally, exclude the webhook route:
import express from 'express';
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
const app = express();
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
}
}
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!,
handler: new MyEventHandler(),
});
// Register webhook BEFORE global JSON parser
app.post(
'/webhooks/authvital',
express.raw({ type: 'application/json' }),
webhookRouter.expressHandler()
);
// Global JSON parser for all other routes
app.use(express.json());
// Your other routes
app.get('/api/users', (req, res) => {
// ...
});
Next.js (App Router)¶
// app/api/webhooks/authvital/route.ts
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
import type { NextRequest } from 'next/server';
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
// Sync to your database
}
async onMemberJoined(event) {
console.log(`${event.data.given_name} joined tenant ${event.tenant_id}`);
}
async onLicenseAssigned(event) {
console.log(`License ${event.data.license_type_name} assigned to ${event.data.sub}`);
}
}
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!,
handler: new MyEventHandler(),
maxTimestampAge: 300,
keysCacheTtl: 3600000,
});
export async function POST(request: NextRequest) {
return webhookRouter.nextjsHandler()(request);
}
// Use Node.js runtime for crypto operations
export const runtime = 'nodejs';
Next.js (Pages Router)¶
// pages/api/webhooks/authvital.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
}
async onMemberJoined(event) {
console.log(`${event.data.given_name} joined tenant`);
}
}
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!,
handler: new MyEventHandler(),
});
// Disable body parsing for signature verification
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
return webhookRouter.nextjsPagesHandler()(req, res);
}
NestJS¶
Module Setup¶
// src/webhooks/webhooks.module.ts
import { Module } from '@nestjs/common';
import { WebhooksController } from './webhooks.controller';
import { WebhookEventHandler } from './webhook-event-handler';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
controllers: [WebhooksController],
providers: [WebhookEventHandler],
})
export class WebhooksModule {}
Event Handler Service¶
// src/webhooks/webhook-event-handler.ts
import { Injectable } from '@nestjs/common';
import { AuthVitalEventHandler } from '@authvital/sdk/webhooks';
import type {
SubjectCreatedEvent,
MemberJoinedEvent,
LicenseAssignedEvent,
} from '@authvital/sdk/webhooks';
import { UsersService } from '../users/users.service';
@Injectable()
export class WebhookEventHandler extends AuthVitalEventHandler {
constructor(private readonly usersService: UsersService) {
super();
}
async onSubjectCreated(event: SubjectCreatedEvent): Promise<void> {
await this.usersService.create({
id: event.data.sub,
email: event.data.email!,
firstName: event.data.given_name,
lastName: event.data.family_name,
});
}
async onMemberJoined(event: MemberJoinedEvent): Promise<void> {
await this.usersService.assignToTenant(
event.data.sub,
event.tenant_id,
event.data.tenant_roles
);
}
async onLicenseAssigned(event: LicenseAssignedEvent): Promise<void> {
await this.usersService.assignLicense(
event.data.sub,
event.data.license_type_id
);
}
}
Controller¶
// src/webhooks/webhooks.controller.ts
import { Controller, Post, Req, Res, RawBodyRequest } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request, Response } from 'express';
import { WebhookRouter } from '@authvital/sdk/webhooks';
import { WebhookEventHandler } from './webhook-event-handler';
@Controller('webhooks')
export class WebhooksController {
private readonly webhookRouter: WebhookRouter;
constructor(
private readonly eventHandler: WebhookEventHandler,
private readonly configService: ConfigService
) {
this.webhookRouter = new WebhookRouter({
authVitalHost: this.configService.getOrThrow('AV_HOST'),
handler: this.eventHandler,
maxTimestampAge: 300,
keysCacheTtl: 3600000,
});
}
@Post('authvital')
async handleWebhook(
@Req() req: RawBodyRequest<Request>,
@Res() res: Response
) {
return this.webhookRouter.expressHandler()(req, res);
}
}
Enable Raw Body in Main¶
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
rawBody: true, // Enable raw body for webhook signature verification
});
await app.listen(3000);
}
bootstrap();
Fastify¶
import Fastify from 'fastify';
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
const fastify = Fastify({
logger: true,
});
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
}
}
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!,
handler: new MyEventHandler(),
});
// Register raw body content type parser
fastify.addContentTypeParser(
'application/json',
{ parseAs: 'buffer' },
(req, body, done) => {
done(null, body);
}
);
fastify.post('/webhooks/authvital', async (request, reply) => {
return webhookRouter.fastifyHandler()(request, reply);
});
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err;
console.log(`Webhook endpoint ready at ${address}/webhooks/authvital`);
});
Hono¶
import { Hono } from 'hono';
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
const app = new Hono();
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
}
}
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!,
handler: new MyEventHandler(),
});
app.post('/webhooks/authvital', async (c) => {
return webhookRouter.honoHandler()(c);
});
export default app;
Koa¶
import Koa from 'koa';
import Router from '@koa/router';
import { WebhookRouter, AuthVitalEventHandler } from '@authvital/sdk/webhooks';
const app = new Koa();
const router = new Router();
class MyEventHandler extends AuthVitalEventHandler {
async onSubjectCreated(event) {
console.log('New user:', event.data.email);
}
}
const webhookRouter = new WebhookRouter({
authVitalHost: process.env.AV_HOST!,
handler: new MyEventHandler(),
});
router.post('/webhooks/authvital', async (ctx) => {
return webhookRouter.koaHandler()(ctx);
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
console.log('Webhook endpoint ready at http://localhost:3000/webhooks/authvital');
});
Related Documentation¶
- Webhooks Guide - Overview and quick start
- Event Types & Payloads - All event types
- Manual Verification - Low-level API
- Best Practices - Error handling, testing