Guides
Next.js App Router Integration
This guide shows you exactly where to add RecurCite SDK calls in a Next.js 13+ application using the App Router. By the end, you'll have full evidence coverage for disputes.
Prerequisites
- Next.js 13+ with App Router
@recurcite/sdkinstalled (npm install @recurcite/sdk)- A RecurCite API key from Dashboard → Developers
- Stripe Billing set up with webhook forwarding
1. Create the SDK singleton
Create a single file that initializes the RecurCite client. Import it anywhere you need to send events:
import { init } from "@recurcite/sdk";
export const recurcite = init({
apiKey: process.env.RECURCITE_API_KEY!,
signingSecret: process.env.RECURCITE_SIGNING_SECRET,
});Server-side only
This file should only be imported in server components, API routes, and server actions. Never import it in client components — it would expose your API key.
2. Track terms acceptance
When a user accepts your Terms of Service, send a terms.accepted event. This is the single strongest piece of evidence — it proves the customer agreed to your policies.
import { recurcite } from "@/lib/recurcite";
import { auth } from "@/lib/auth";
export async function POST(req: Request) {
const session = await auth();
if (!session?.user) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
const { termsVersion } = await req.json();
await recurcite.track({
type: "terms.accepted",
payload: {
version: termsVersion,
accepted_at: new Date().toISOString(),
},
stripe_refs: {
stripe_customer_id: session.user.stripeCustomerId,
},
});
return Response.json({ success: true });
}3. Track logins
Send a user.login event every time a user authenticates. If you use NextAuth/Auth.js, add it to the signIn callback:
import { recurcite } from "@/lib/recurcite";
// Inside your NextAuth config:
callbacks: {
async signIn({ user, account }) {
// Track login for evidence
if (user.stripeCustomerId) {
await recurcite.track({
type: "user.login",
payload: {
occurred_at: new Date().toISOString(),
// IP and user_agent come from the request
// You may need to pass these through context
},
stripe_refs: {
stripe_customer_id: user.stripeCustomerId,
},
});
}
return true;
},
}Tip
Alternatively, track logins in a dedicated API route so you can access req.ip and req.headers["user-agent"]. IP addresses are especially valuable for CE3 matching.
4. Track product usage
Send product.used events for key feature usage. This proves the customer derived value from your service:
import { recurcite } from "@/lib/recurcite";
import { auth } from "@/lib/auth";
export async function POST(req: Request) {
const session = await auth();
if (!session?.user) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
const { featureKey, count } = await req.json();
await recurcite.track({
type: "product.used",
payload: {
feature_key: featureKey,
count: count || 1,
occurred_at: new Date().toISOString(),
},
stripe_refs: {
stripe_customer_id: session.user.stripeCustomerId,
},
});
return Response.json({ success: true });
}For high-throughput usage, batch events to reduce API calls:
import { recurcite } from "@/lib/recurcite";
export async function trackDailyUsage(
stripeCustomerId: string,
features: { key: string; count: number }[]
) {
await recurcite.batchTrack(
features.map((f) => ({
type: "product.used" as const,
payload: {
feature_key: f.key,
count: f.count,
occurred_at: new Date().toISOString(),
},
stripe_refs: { stripe_customer_id: stripeCustomerId },
}))
);
}5. Track cancellations
Track both the cancellation request and confirmation. This proves you had a proper cancellation flow:
import { recurcite } from "@/lib/recurcite";
import { auth } from "@/lib/auth";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const session = await auth();
const { subscriptionId } = await req.json();
// 1. Track cancellation request
await recurcite.track({
type: "cancellation.requested",
payload: { occurred_at: new Date().toISOString() },
stripe_refs: {
stripe_customer_id: session!.user.stripeCustomerId,
stripe_subscription_id: subscriptionId,
},
});
// 2. Cancel in Stripe
const sub = await stripe.subscriptions.cancel(subscriptionId);
// 3. Track cancellation confirmed
await recurcite.track({
type: "cancellation.confirmed",
payload: {
occurred_at: new Date().toISOString(),
receipt_id: sub.id,
},
stripe_refs: {
stripe_customer_id: session!.user.stripeCustomerId,
stripe_subscription_id: subscriptionId,
},
});
return Response.json({ success: true });
}6. Track transactions for CE3
For CE3 eligibility, send a transaction.completed event on every successful charge. The best place is your Stripe webhook handler for invoice.paid:
case "invoice.paid":
case "invoice.payment_succeeded": {
const invoice = event.data.object as Stripe.Invoice;
await recurcite.track({
type: "transaction.completed",
payload: {
charge_id: invoice.charge as string,
amount: invoice.amount_paid,
currency: invoice.currency,
occurred_at: new Date(invoice.created * 1000).toISOString(),
// CE3 signals — include as many as possible:
ip: customerIp, // from your session/auth
email_sha256: customerEmailSha256, // SHA-256(email)
product_description: invoice.lines.data[0]?.description ?? "",
},
stripe_refs: {
stripe_customer_id: invoice.customer as string,
stripe_subscription_id: invoice.subscription as string,
stripe_invoice_id: invoice.id,
},
});
break;
}CE3 signal importance
CE3 requires at least 2 prior undisputed transactions with matching IP or email signals. Send this event on every charge to build a strong signal history. See Concepts → CE3 for full details.
Environment variables
# RecurCite
RECURCITE_API_KEY=rc_live_your_api_key_here
RECURCITE_SIGNING_SECRET=your_signing_secret_here # Optional but recommendedIntegration checklist
| Event | Where | Priority |
|---|---|---|
terms.accepted | ToS checkbox / acceptance endpoint | Critical |
user.login | NextAuth signIn callback or login API route | Critical |
product.used | Feature usage API routes / middleware | High |
transaction.completed | Stripe invoice.paid webhook | High (CE3) |
cancellation.* | Cancel subscription endpoint | High |
support.ticket.* | Support system webhooks or API | Medium |
Next steps
- Event Reference — all event types with required fields
- Test Mode — verify your integration with Stripe CLI
- Security & Data — enable HMAC signing for tamper-proof evidence