# Stripe

> Track sales from Stripe Checkout and Payment Links with a webhook – one-off purchases, subscriptions and refunds.

If your customers pay through Stripe Checkout or Payment Links on your own site, Stripe reports each sale straight to us through a webhook. That covers one-off purchases, subscriptions and renewals. You don't need a tracking script on the page to report the sale – but the click ID has to reach Stripe, and that takes a little code.

> [!NOTE]
> If the shop runs on Shopify, WooCommerce or one of the Danish platforms, choose that platform in Setup – even if payments go through Stripe. Only choose **Stripe** when customers pay in Stripe Checkout or through Payment Links.

## Before you start

- Access to your Stripe dashboard (Workbench).
- Access to the code that creates Checkout Sessions – or to your Payment Links. This is usually a job for your developer, and Setup can send the guide straight to them.
- Your store ID. The address under **Program → Setup** already has it filled in; on this page it appears as the placeholder `DIN_BUTIK`.

In Setup, Stripe has three steps: **Choose platform**, **Stripe setup** (webhook and signing secret) and **Confirm** (the test).

We never ask for your Stripe API key. All you give us is the webhook's signing secret, which can only confirm that a message really comes from you.

## 1. Create the webhook

In Stripe, go to **Workbench → Webhooks → Create an event destination**.

1. Choose **Your account** and the **Snapshot** payload format – not Thin, which Stripe suggests. Thin leaves out the order data, and without it the sale can't be recorded.
2. Select the events (listed below).
3. Choose **Webhook endpoint** and use this URL:

```text title="Webhook URL"
https://app.affilyflow.com/api/stripe/webhook/DIN_BUTIK
```

**Always:**

- `checkout.session.completed`
- `checkout.session.async_payment_succeeded`
- `charge.refunded`
- `charge.dispute.created`

**Only for subscriptions:**

- `invoice.payment_succeeded`
- `invoice_payment.paid`
- `customer.subscription.deleted`

`invoice_payment.paid` gives us the payment reference on subscription payments, so a refund or dispute on a renewal can be matched to the sale – without it, the commission stays in place.

> [!WARNING]
> The refund events matter: without them, a refunded purchase or a dispute turns into commission you have to pay. And `checkout.session.async_payment_succeeded` catches payments that only complete later (such as bank transfers) – without it, those sales are never recorded.

## 2. Add the signing secret

Copy the endpoint's **Signing secret** (it starts with `whsec_`) and paste it under **Setup → Stripe** in Affilyflow. Without it, we reject every event. Don't send it by email – the store owner pastes it in themselves.

If you also want to test in Stripe's test mode, create a matching event destination in test mode and paste its signing secret into the same field, separated by a space. We try both.

## 3. Pass the click ID along

Affiliates send customers to your site with `?afid=…` in the address. Store the click ID in a cookie on your main domain when the customer lands, so it survives the move from www to, say, an app subdomain:

```js title="Every page"
// On every page: keep the click ID from ?afid= in a cookie on your main domain (90 days)
const afid = new URLSearchParams(location.search).get('afid');
if (afid && /^c_[A-Za-z0-9_-]{4,64}$/.test(afid)) {
  const domain = location.hostname.split('.').slice(-2).join('.');
  document.cookie = 'af_click=' + afid + '; domain=.' + domain + '; path=/; max-age=7776000; SameSite=Lax; Secure';
}
// On your server, read the af_click cookie from the request when creating the Checkout Session.
```

Then pass it as `client_reference_id` when you create the Checkout Session:

```js title="client_reference_id"
// stripe.checkout.sessions.create({ ...,
client_reference_id: req.cookies.af_click || undefined,
// })
```

- The domain is taken as the last two parts of the hostname. If your domain has more parts (such as `.co.uk`), change it to your own.
- If you create the session on the server, the browser has to send the cookie along with the request.
- If you use **Payment Links**, add the click ID to the link as `?client_reference_id=…`. The easiest way: put the [tracking code](https://www.affilyflow.com/en/docs/reference/track-js) on every page – it stores the click itself and adds it automatically to every link to `buy.stripe.com`, including links rendered later.

Afterwards, check in Stripe that `client_reference_id` on the `checkout.session.completed` event is `"c_…"` and not `null`. If it is `null`, the click ID never arrived – check the cookie domain.

## Test

The test is in the **Confirm** step in Setup. You can test in Stripe's test mode without moving any money:

1. Press **Open test link** in Setup.
2. Complete a one-off purchase in Stripe Checkout with a [test card](https://docs.stripe.com/testing) (e.g. 4242 4242 4242 4242) in the same browser. The webhook also has to receive test events – create it in test mode, or create a matching one for testing (see step 2).
3. The live test in Setup shows the test click and the purchase as a test sale: no commission, no fee, no emails.

In test mode, only a Checkout purchase made after a click on the test link is recorded – everything else in test mode is ignored. If you would rather test in live mode, buy something cheap with a real card after opening the test link, and refund it in Stripe afterwards.

Without a test, tracking is confirmed on its own by the first affiliate purchase. Your program becomes visible to affiliates once we have received the first event from your webhook.

## What Stripe reports

- **The amount.** Commission is calculated on the payment amount excluding VAT, taken directly from Stripe.
- **Currency.** Sales in other currencies are converted to Danish kroner at the ECB rate at the time of the sale, and the rate is locked on the sale.
- **Subscriptions.** Turn on **Recurring commission (subscriptions)** under **Program**, and every renewal automatically earns commission for as long as the program says. The same invoice can never count twice. Subscriptions that are not created through Stripe Checkout are not covered.
- **Trial periods.** If the subscription starts with a free trial, the sale is only created at the first paid invoice. A renewal of DKK 0 earns no commission.
- **Refunds.** A full refund or a dispute rejects the sale, and the affiliate is notified. A partial refund reduces commission and fee in the same proportion. If the program pays a fixed amount per sale, a partial refund doesn't change it. For subscriptions, this requires `invoice_payment.paid` on the webhook – without it, a refund of a renewal can't be matched to the sale.

## Personal discount codes

A [personal discount code](https://www.affilyflow.com/en/docs/store/personal-discount-codes) earns the affiliate commission, even without a click. On Stripe, this only works for **one-off payments** – subscriptions still need a click.

Stripe only sends us the coupon's **ID**, not the promotion code the customer typed. So:

1. Create the code under **Program → Discount codes** in Affilyflow, and choose the affiliate under **Who is the code for?**.
2. Create the coupon in Stripe with **the same ID** as the code – e.g. `ANNA10`. The promotion code the customer types can have the same name.

If the customer has a valid click, the click always wins. If the click has expired, or there is no click, the sale is credited to the affiliate the coupon ID belongs to. The webhook address contains your store ID (as above), so we know which store the code belongs to.

Source: https://www.affilyflow.com/en/docs/store/stripe
