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/core

Usage

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 });