Connect a payment to its first visit
Read the visitor cookie on your server and pass RouteRev metadata to Stripe Checkout.
Keep identity on your server
Read _rr_vid from the incoming request’s cookies; do not ask the browser to supply metadata in a checkout body. Use your authenticated account ID for rr_user_id. The visitor cookie is an attribution signal, not authentication.
For subscriptions, place the same metadata in subscription_data.metadata as well as the Checkout session. RouteRev records subscription payments from invoices; invoice attribution reads subscription metadata. For a one-time payment, use mode: payment and session metadata.
Next.js route handler
Adapt your existing server Checkout handler. checkoutContext is your own app helper: it must authenticate the user and provide a trusted price and return URLs. STRIPE_SECRET_KEY is a server-only setting in your product, never part of the RouteRev snippet or client bundle.
import Stripe from 'stripe';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
import { checkoutContext } from './checkout-context';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
// Your own authenticated user, server-side price and return URLs.
const { userId, priceId, successUrl, cancelUrl } =
await checkoutContext(request);
const visitorId = (await cookies()).get('_rr_vid')?.value;
const metadata = {
...(visitorId ? { rr_visitor_id: visitorId } : {}),
rr_user_id: userId,
};
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
metadata,
subscription_data: { metadata },
success_url: successUrl,
cancel_url: cancelUrl,
});
return NextResponse.json({ url: session.url });
}Plain Node example
Pass a Stripe client and trusted checkout context from your existing server. This helper extracts only _rr_vid from the Cookie header and ignores a malformed encoded cookie.
export async function createCheckout(stripe, context) {
// cookieHeader comes from your server request, not a client body.
// userId, priceId and return URLs come from your trusted app logic.
const { cookieHeader, userId, priceId, successUrl, cancelUrl } = context;
const cookie = (cookieHeader || '').split(';')
.map(part => part.trim()).find(part => part.startsWith('_rr_vid='));
let visitorId;
try { visitorId = cookie ? decodeURIComponent(cookie.slice(8)) : undefined; }
catch { visitorId = undefined; }
const metadata = {
...(visitorId ? { rr_visitor_id: visitorId } : {}),
rr_user_id: userId,
};
return stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
metadata,
subscription_data: { metadata },
success_url: successUrl,
cancel_url: cancelUrl,
});
}Connect the webhook
Connecting the webhook is done with us during early access. RouteRev receives invoice.paid, checkout.session.completed and charge.refunded at /api/hooks/stripe/<product-slug>, verifying each product’s webhook signature.
Checkout metadata is not automatically copied to every Stripe charge. Refund attribution depends on the metadata present on the charge; ask us to check it with your integration.