Next.js (App Router)
This recipe checks signups in an App Router route handler. It works the same with any auth library that lets you run code before creating the user.
1. A small client with fail-open
// lib/burnguard.ts
export type BurnGuardDecision = "allow" | "limit" | "review" | "block";
export interface BurnGuardResult {
decision: BurnGuardDecision;
recommended_credit_fraction?: number;
reasons?: { code: string; weight: number; detail: string }[];
id?: string;
}
export async function checkSignup(input: {
email: string;
ip?: string;
deviceToken?: string;
userAgent?: string;
externalId?: string;
}): Promise<BurnGuardResult> {
try {
const res = await fetch(`${process.env.BURNGUARD_URL}/v1/check`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BURNGUARD_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
event: "signup",
email: input.email,
ip: input.ip,
device_token: input.deviceToken,
user_agent: input.userAgent,
external_id: input.externalId,
}),
signal: AbortSignal.timeout(1500),
cache: "no-store",
});
if (!res.ok) return { decision: "allow" };
return (await res.json()) as BurnGuardResult;
} catch {
return { decision: "allow" }; // never block signups because of us
}
}
2. The signup route
// app/api/signup/route.ts
import { NextResponse } from "next/server";
import { checkSignup } from "@/lib/burnguard";
const FREE_CREDITS = 500;
export async function POST(req: Request) {
const { email, password, deviceToken } = await req.json();
const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
const check = await checkSignup({
email,
ip,
deviceToken,
userAgent: req.headers.get("user-agent") ?? undefined,
});
if (check.decision === "block") {
return NextResponse.json({ error: "We couldn't create this account." }, { status: 403 });
}
const credits =
check.decision === "review" ? 0 : Math.floor(FREE_CREDITS * (check.recommended_credit_fraction ?? 1));
const user = await createUser({ email, password, credits, needsVerification: check.decision === "review" });
return NextResponse.json({ id: user.id });
}
Only trust x-forwarded-for when your host (Vercel, a load balancer, nginx) sets it.
3. The device token on the client
"use client";
import Script from "next/script";
export function BurnGuardScript() {
return <Script src={`${process.env.NEXT_PUBLIC_BURNGUARD_URL}/sdk.js`} data-key={process.env.NEXT_PUBLIC_BURNGUARD_PK} strategy="afterInteractive" />;
}
// In your signup handler:
const deviceToken = await (window as any).BurnGuard?.token().catch(() => undefined);
See Decisions and credit gating for what to do with each decision.