Fastify SDK
Integrate Perly churn prevention into your Fastify application using a preHandler hook and user resolver.
@perly/fastifyv1.0.03 minutes
Installation
npm install @perly/fastify @perly/coreUsage
Create a Perly client with createPerlyClient and register the perlyHook as a Fastify preHandler hook. The hook resolves the current user via your userResolver and attaches a perly client to every request.
// server.ts
import Fastify from 'fastify';
import { createPerlyClient, perlyHook, PerlyBuilder } from '@perly/fastify';
const perly = createPerlyClient({
apiKey: process.env.PERLY_API_KEY!,
userResolver: (req) => {
const userId = req.headers['user-id'] as string;
if (!userId) return null;
return new PerlyBuilder().setId(userId).build();
},
});
const fastify = Fastify();
// hooks to authenticate users and other prehandle filters,
// so that perly only gets the requests it should
fastify.addHook('preHandler', perlyHook(perly));
fastify.listen({ port: 3000 });User Resolver
The userResolver is a function passed to createPerlyClient. It receives the Fastify FastifyRequest object and returns a Perly user built with PerlyBuilder, or null to skip tracking.
import { PerlyBuilder, PerlyUser } from '@perly/core';
import { FastifyRequest } from 'fastify';
function resolvePerlyUser(req: FastifyRequest): PerlyUser | null {
const user = req.user;
if (!user) return null;
return new PerlyBuilder()
.setId(user.id)
.setMetadata({
plan: user.plan,
region: user.region,
companyId: user.companyId,
})
.linkStripeById(user.stripeCustomerId)
.linkHubspotById(user.hubspotContactId)
.build();
}Tracking Events
Access request.perly in any route handler to track customer engagement events.
fastify.post('/onboarding/complete', async (request, reply) => {
await request.perly.track('onboarding_completed');
return { success: true };
});
fastify.post('/reports/export', async (request, reply) => {
await request.perly.track('report_exported', {
format: request.body.format,
});
return { url: reportUrl };
});
fastify.post('/team/invite', async (request, reply) => {
await request.perly.track('team_member_invited', {
email: request.body.email,
});
return { invited: true };
});Expansion Signals
Send signals when customers approach their plan limits. These trigger upsell workflows inside Perly.
fastify.post('/api/check-limits', async (request, reply) => {
const seats = await getSeatCount(request.user.companyId);
if (seats.current > seats.limit * 0.9) {
await request.perly.signal('seat_limit_near', {
current: seats.current,
limit: seats.limit,
});
}
return { seats };
});
// Other signal types
await request.perly.signal('rate_limit_hit', { endpoint: '/api/search' });
await request.perly.signal('storage_limit_near', { usedGb: 9.2, limitGb: 10 });
await request.perly.signal('token_usage_high', { current: 950000, limit: 1000000 });