> ## Documentation Index
> Fetch the complete documentation index at: https://learn.biq.li/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe

> Attribute verified Stripe payments with server-side tracking.

Send Stripe conversions from a verified webhook. This keeps payment authority on your server and still records a sale when the checkout tab closes or browser tracking is blocked.

## Preserve attribution before checkout

1. Let the browser SDK capture `bq_id`.
2. Associate it with your authenticated customer, checkout record, or Stripe metadata.
3. Keep your own stable customer ID as `customerExternalId`.
4. Retrieve both values when Stripe sends the payment webhook.

Never trust a browser message alone as proof of payment.

## Verify and process the webhook

```ts theme={null}
import Stripe from 'stripe';
import {Biqli} from '@biqli/sdk';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const biqli = new Biqli({apiKey: process.env.BIQLI_API_KEY!});

export async function handleStripeWebhook(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get('stripe-signature')!;
  const event = stripe.webhooks.constructEvent(
    rawBody,
    signature,
    process.env.STRIPE_WEBHOOK_SECRET!,
  );

  if (event.type !== 'checkout.session.completed') return;

  const session = event.data.object;

  await biqli.track.sale({
    clickId: session.metadata?.biqli_click_id,
    customerExternalId: session.metadata?.customer_external_id
      ?? String(session.customer),
    amount: session.amount_total ?? 0,
    currency: session.currency ?? 'usd',
    paymentProcessor: 'stripe',
    invoiceId: String(session.invoice ?? session.id),
  });
}
```

Verify Stripe's signature against the exact raw request body before reading or acting on the event.

<Note>
  Stripe's `amount_total` is already an integer minor-unit amount. Do not multiply it by 100.
</Note>

## Subscriptions and retries

* Send each paid invoice with its stable Stripe invoice ID.
* Use `paymentProcessor: 'stripe'` consistently.
* Let exact Stripe webhook retries call Biqli again with the same data.
* Do not create a sale for an unpaid or failed invoice.
* Decide how refunds affect your reporting before implementing a separate refund workflow.

## Test before launch

Use Stripe test mode to complete a checkout through a conversion-enabled Biqli link. Confirm one customer, one expected lead if your flow sends one, and one sale appear in Biqli. Replay the Stripe event and confirm revenue does not increase again.
