# Stripe Checkout API (https://www.getfounderhq.com/docs/analytics/revenue/connect-stripe/checkout-api)

Attribute payments from Checkout Sessions that your own server creates. This
takes about ten minutes.

<Callout title="Before you start">
[Connect Stripe](/analytics/revenue/connect-stripe) first. Install the
[web SDK](/analytics/sdks/web) on your pricing page and the
[Node SDK](/analytics/sdks/node) on your server.
</Callout>

## 1. Send the visitor IDs to your server

The browser holds the two IDs that identify the visitor. Read them with
`checkoutMetadata()` and post them with the rest of the checkout request.

```tsx
// app/pricing/checkout-button.tsx
"use client";

import { founderhq } from "@founderhq/events";

export function CheckoutButton({ priceId }: { priceId: string }) {
  async function checkout() {
    const identity = founderhq.checkoutMetadata();
    const response = await fetch("/api/checkout", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ priceId, identity }),
    });
    const { url } = await response.json();
    window.location.href = url;
  }

  return <button onClick={checkout}>Start the growth plan</button>;
}
```

## 2. Place the metadata on the session and on the renewal object

Put the same metadata in two places. Session metadata alone does not survive
renewals, so the second placement is what keeps a subscription attributed.

```ts
// app/api/checkout/route.ts
import Stripe from "stripe";
import { checkoutMetadata } from "@founderhq/events-node";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const { priceId, identity } = await request.json();
  const attribution = checkoutMetadata({
    anonymousId: identity.fhq_anonymous_id,
    sessionId: identity.fhq_session_id,
  });

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: "https://acme.com/thanks",
    cancel_url: "https://acme.com/pricing",
    metadata: attribution,
    subscription_data: { metadata: attribution },
  });

  return Response.json({ url: session.url });
}
```

Selling a one-time product? Use `payment_intent_data: { metadata: attribution }`
in place of `subscription_data`.

`checkoutMetadata()` throws when either ID is empty. Catch that and create the
session without the metadata rather than blocking the sale.

## 3. Let renewals resolve themselves

FounderHQ binds the Stripe customer and subscription on the first payment.
Later renewals arrive with no browser session, and FounderHQ resolves them
through that binding.

## Verify

1. Start a checkout on your pricing page.
2. In Stripe, open the Checkout Session. The metadata shows
   `fhq_anonymous_id` and `fhq_session_id`.
3. Open the created subscription. It shows the same two keys.
4. Pay with a test card. In FounderHQ, the payment shows the channel that
   brought the buyer.

## Troubleshoot

### The session has metadata but the subscription does not

You set `metadata` only. Add `subscription_data.metadata` with the same object
and create a new session.

### The payment arrives with no channel

The browser sent empty IDs. `checkoutMetadata()` in the browser returns an
empty object when the visitor denied consent. Check consent before you blame
the server.
