Render a handoff button
A handoff lets the buyer invite someone else to pay for their purchase. Create one Proxy session for the intended purchase, store its handoffUrl, and give that URL to the buyer to share.
Prerequisites
Section titled “Prerequisites”Before creating a handoff:
- Complete Set up Stripe.
- Find your integration path so you know which cart snapshot shape to store.
- Create a durable purchase-request record in your system. You will use its ID to recover the same Proxy session when the buyer retries or invites another payer.
Create the Proxy client
Section titled “Create the Proxy client”Create the Proxy client in a server-only module. The path guide creates its own Stripe client with that path’s request API version; do not reuse a Stripe client configured for a different path.
// server/proxy.tsimport { createProxyCheckoutServerClient } from "@proxy-checkout/server-js";
export const proxy = createProxyCheckoutServerClient({ apiKey: process.env.PROXY_SECRET_KEY!, publishableKey: process.env.PROXY_PUBLISHABLE_KEY!,});Add the buyer action
Section titled “Add the buyer action”Add a delegated payment button to the checkout page the buyer already uses. Proxy does not render this button. When the buyer selects it, call your backend and present the returned handoffUrl by copying it, showing a modal, or opening a mobile share sheet.
type HandoffResponse = { expiresAt: string; handoffUrl: string; proxySessionId: string;};
async function startDelegatedPayment( payerEmail?: string,): Promise<HandoffResponse | null> { const response = await fetch("/api/proxy/handoffs", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ payerEmail }), });
if (!response.ok) { return null; }
return (await response.json()) as HandoffResponse;}Create or recover the handoff
Section titled “Create or recover the handoff”Implement the endpoint your buyer action calls. Authenticate the buyer, load the durable purchase request and current cart, then call createHandoff. This example uses the cart shape shared by the one-time payment paths.
// POST /api/proxy/handoffsimport { proxy } from "./proxy";
export async function POST(request: Request) { const { payerEmail } = (await request.json()) as { payerEmail?: string };
// Use the buyer from your current checkout session. const buyer = { email: "recipient@example.com", id: "buyer_123" }; // Load the durable purchase-request row for this intended order. const purchaseRequest = { id: "purchase_request_123" }; // Use the cart shown in your checkout. const cartSnapshot = { currency: "usd", lineItems: [ { offerId: "annual-membership", name: "Annual membership", amountMinor: 5000, quantity: 1, }, ], }; const amountMinor = cartSnapshot.lineItems.reduce( (total, item) => total + item.amountMinor * item.quantity, 0, );
const handoff = await proxy.sessions.createHandoff({ amountMinor, beneficiaryContact: { email: buyer.email }, buyerReference: buyer.id, cartSnapshot, currency: cartSnapshot.currency, idempotencyKey: `proxy-session:${purchaseRequest.id}`, payerContact: payerEmail ? { email: payerEmail } : undefined, });
// Store handoff.id and handoff.handoffUrl on the purchase-request row. return Response.json({ expiresAt: handoff.expiresAt, handoffUrl: handoff.handoffUrl, proxySessionId: handoff.id, });}Adjust the proxy import path to match your server project. The cart snapshot is checkout context that Proxy returns to your selected path later; it does not replace your pricing logic. Re-check price, eligibility, and availability before creating anything in Stripe.
Persist handoff.id and handoff.handoffUrl before sharing them. buyerReference identifies who receives the entitlement. The durable purchase-request ID identifies this intended order and supplies the session idempotency key. Reuse the same purchase-request ID for retries and every invited payer. Use a new purchase-request ID for a genuinely new purchase, even when the buyer, offer, and amount are identical.
Use a stable, non-PII account or profile ID as buyerReference. For a pre-account purchase, create a pending entitlement first and use its ID. Do not use an email address as the reference.
Use a subscription cart snapshot
Section titled “Use a subscription cart snapshot”The endpoint above stores a one-time cart. For a subscription, replace that cartSnapshot before calling createHandoff. Load Stripe Price IDs from trusted server configuration or your merchant-owned catalog—never accept them from the payer browser.
For a subscription created by Stripe Checkout, store the recurring Price:
const amountMinor = 7500; // Calculate from your trusted catalog.const initialOneTimePriceId = process.env.STRIPE_INITIAL_ONE_TIME_PRICE_ID;const cartSnapshot = { currency: "usd", ...(initialOneTimePriceId ? { initialOneTimePriceId } : {}), recurringPriceId: process.env.STRIPE_RECURRING_PRICE_ID!, quantity: 1,};Set STRIPE_INITIAL_ONE_TIME_PRICE_ID only when the first Invoice includes a one-time Price. Omit it for a recurring-only subscription.
For a direct subscription using a SetupIntent or an already-authorized saved payment method, store the fixed recurring lines:
const amountMinor = 5000; // Calculate from your trusted catalog.const cartSnapshot = { currency: "usd", lines: [ { priceId: process.env.STRIPE_RECURRING_PRICE_ID!, quantity: 1, }, ], trialDays: 0,};Use the initial amount expected for this purchase. Use 0 when a supported free trial or zero-value initial invoice makes nothing due now. Stripe Checkout subscription paths use this snapshot to create a Checkout Session. Direct subscription paths use it to create the Subscription.
Verify the handoff
Section titled “Verify the handoff”- Create a handoff from the buyer checkout and store the returned Proxy session ID and
handoffUrl. - Retry the same purchase request and confirm Proxy returns the same session and handoff URL.
- Open the handoff URL and confirm Proxy sends the payer to your configured checkout URL with the same
proxy_session_id. - Open the same handoff in a second browser and confirm both browsers load the same purchase.
The shared handoff is ready. Continue to the Stripe path you selected to retrieve the session and open the matching Stripe payment flow.