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

# Stripe

> Attribute Stripe payments and subscriptions back to the install and ad campaign that drove them.

With the Stripe integration, you can:

1. Attribute payments and subscriptions collected through Stripe back to the install and ad campaign that drove them.
2. See Stripe revenue alongside your app-store revenue in Appstack.

<Note>
  **To successfully connect Stripe, you must:**

  1. Have Owner/Admin access to an Appstack organization.

  * Have your own backend create the Stripe objects your app charges through (Checkout Sessions, Subscriptions, or PaymentIntents). This integration is server-side, and can't be done from the app alone.
</Note>

<Note>
  This page covers attaching the Appstack ID to your Stripe data. If you haven't installed the Appstack app from the Stripe App Marketplace and connected your account to your organization yet, do that first. From Appstack, go to **Integrations** > **Stripe.**
</Note>

## Why this needs your backend

Every other Appstack integration reads a value your app's SDK already has. Stripe is different: the objects that generate your revenue events (Customer, Subscription, Checkout Session) are created by your server, not by code running on the device. There's no SDK hook that can reach them.

Stripe also enforces this at the API level: it only returns an object's metadata to requests made with your secret key. A publishable key (the kind that ships inside a mobile app) never sees it, even if you try. So this has to be implemented once, in whatever backend code creates your Stripe objects.

The upside is that it's a small amount of work. Attach the ID once, when the customer checks out, and Stripe carries it forward automatically for the life of that subscription. You never touch it again for that customer, including at renewal, a year later.

## Connect to Stripe

Follow the steps to attribute your Stripe revenue:

<Steps>
  <Step title="Get the Appstack ID in your app">
    Read the Appstack ID on-device, the same way you would for any partner integration, and send it to your own backend along with whatever request kicks off checkout.

    This isn't a new endpoint: your app already calls your own backend to create the Checkout Session, since only server-side code can hold a Stripe secret key. Add the Appstack ID as one more field on that existing request.

    <CodeGroup>
      ```swift Swift theme={null}
      let appstackId = AppstackAttributionSdk.shared.getAppstackId()
      // Send appstackId to your backend along with the checkout request.
      ```

      ```kotlin Kotlin theme={null}
      val appstackId = AppstackAttributionSdk.getAppstackId()
      // Send appstackId to your backend along with the checkout request.
      ```

      ```typescript React Native theme={null}
      const appstackId = await AppstackAttributionSdk.getAppstackId();
      // Send appstackId to your backend along with the checkout request.
      ```

      ```dart Flutter theme={null}
      final appstackId = await AppstackAttributionSdk.instance.getAppstackId();
      // Send appstackId to your backend along with the checkout request.
      ```
    </CodeGroup>
  </Step>

  <Step title="Attach it when you create the Checkout Session">
    Which parameter you use depends on what you're selling. Use exactly the key name `appstack_id`: that's what Appstack reads back out on our side.

    **Subscriptions** — set it under `subscription_data.metadata`. Stripe copies it onto the Subscription it creates, and from there onto every invoice that subscription ever generates, including renewals:

    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.stripe.com/v1/checkout/sessions \
        -u "sk_live_...:" \
        -d mode=subscription \
        -d "line_items[0][price]={{PRICE_ID}}" \
        -d "line_items[0][quantity]=1" \
        -d "success_url=https://example.com/success" \
        -d "subscription_data[metadata][appstack_id]={{APPSTACK_ID}}"
      ```

      ```javascript Node.js theme={null}
      const session = await stripe.checkout.sessions.create({
        mode: "subscription",
        line_items: [{ price: priceId, quantity: 1 }],
        success_url: "https://example.com/success",
        subscription_data: {
          metadata: { appstack_id: appstackId },
        },
      });
      ```

      ```python Python theme={null}
      session = stripe.checkout.Session.create(
          mode="subscription",
          line_items=[{"price": price_id, "quantity": 1}],
          success_url="https://example.com/success",
          subscription_data={"metadata": {"appstack_id": appstack_id}},
      )
      ```
    </CodeGroup>

    **One-time payments** — set it under `payment_intent_data.metadata` instead. Stripe copies it onto the PaymentIntent, and from there onto the resulting Charge:

    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.stripe.com/v1/checkout/sessions \
        -u "sk_live_...:" \
        -d mode=payment \
        -d "line_items[0][price]={{PRICE_ID}}" \
        -d "line_items[0][quantity]=1" \
        -d "success_url=https://example.com/success" \
        -d "payment_intent_data[metadata][appstack_id]={{APPSTACK_ID}}"
      ```

      ```javascript Node.js theme={null}
      const session = await stripe.checkout.sessions.create({
        mode: "payment",
        line_items: [{ price: priceId, quantity: 1 }],
        success_url: "https://example.com/success",
        payment_intent_data: {
          metadata: { appstack_id: appstackId },
        },
      });
      ```

      ```python Python theme={null}
      session = stripe.checkout.Session.create(
          mode="payment",
          line_items=[{"price": price_id, "quantity": 1}],
          success_url="https://example.com/success",
          payment_intent_data={"metadata": {"appstack_id": appstack_id}},
      )
      ```
    </CodeGroup>

    **Using a Payment Link instead of your own backend?** You won't have server code running at click time, so use the `client_reference_id` URL parameter instead:

    The link has to be built dynamically in your app at open time, with the real ID inserted: a static link copied from the Stripe dashboard has nowhere to put a per-user value.

    ```text theme={null}
    https://buy.stripe.com/your_link?client_reference_id=APPSTACK_ID_HERE
    ```

    This only reaches the initial purchase, not renewals. See the note below.

    <Warning>
      Setting the top-level `metadata` on the Checkout Session is not enough: that only reaches the `checkout.session.completed` event. Use `subscription_data.metadata` or `payment_intent_data.metadata` so Stripe copies it onto the underlying object, or the ID won't be there on any of the events that follow.
    </Warning>

    <Note>
      **Why setting it once at checkout is enough**

      Stripe takes an immutable snapshot of a subscription's metadata onto every invoice it generates, for as long as that subscription exists. You don't need to re-attach anything on renewal. Stripe does it automatically.
    </Note>

    <Note>
      **Payment Links carry less**

      `client_reference_id` only reaches the `checkout.session.completed` event. It never propagates to invoices, so it can attribute the first purchase but not a subscription's renewals. It also silently drops values with characters outside `A-Za-z0-9-_`, with no error, so double-check the ID reaches Stripe intact.
    </Note>
  </Step>
</Steps>

## List of events

These are the raw Stripe events Appstack reads once your integration is set up. There's no separate Appstack-specific naming for Stripe yet: you'll see these under their normal Stripe names.

| Name                            | Definition                                                                                                                                                                             |
| :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `checkout.session.completed`    | Fired once, when a customer finishes checking out. The ID set here reaches only this event; use the metadata parameters above for anything that needs to survive past this point.      |
| `customer.subscription.created` | Fired when a new subscription starts. Carries the ID if `subscription_data.metadata` was set at checkout.                                                                              |
| `invoice.paid`                  | Fired each time an invoice is successfully paid: the initial invoice and every renewal after it. This is where subscription revenue is read from.                                      |
| `invoice.payment_failed`        | Fired when a renewal attempt fails to charge the customer.                                                                                                                             |
| `customer.subscription.deleted` | Fired when a subscription is canceled or ends.                                                                                                                                         |
| `charge.succeeded`              | Fired for a one-time payment. Carries the ID if `payment_intent_data.metadata` was set. Does not carry it for a subscription's renewal charges: read `invoice.paid` for those instead. |

## Troubleshooting

### **General tips**

1. Set it server-side, not client-side. A publishable key can't read or write metadata: attempting this from the app itself fails silently or is rejected by Stripe, depending on the call. It has to run wherever your backend creates the Checkout Session.
2. Use the indirect parameter, not the top-level one. `subscription_data.metadata` / `payment_intent_data.metadata`, not the Checkout Session's own `metadata` field. See the warning above.
3. Use the exact key `appstack_id`. A typo or different casing means Appstack never finds it, with no error on either side.
4. Read invoices for subscription revenue, not charges. A subscription's renewal charges don't carry your metadata: only the invoice does.
5. Contact support: if issues persist, reach out with specific error messages at [support@appstack.tech](mailto:support@appstack.tech).

### Common issues

| Issue                                                         | How to fix                                                                                                                                                                                                                             |
| :------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ID appears on `checkout.session.completed` but nowhere else   | You set the Checkout Session's top-level `metadata` instead of `subscription_data.metadata` / `payment_intent_data.metadata`. The top-level field never propagates past that one event.                                                |
| ID is on the first invoice but missing from later renewals    | Confirm you're reading `parent.subscription_details.metadata` (or the equivalent field for your account's API version) on the invoice, not the invoice's own top-level `metadata`, which Stripe never populates from the subscription. |
| ID missing from `charge.succeeded` for a subscription payment | Expected. Renewal charges are created by Stripe, not by your backend, so there's nothing for the metadata to copy from. Read `invoice.paid` instead.                                                                                   |
| ID missing after using a Payment Link                         | Check the `client_reference_id` value only contains letters, numbers, dashes, and underscores: anything else is dropped silently by Stripe, and note this path doesn't cover renewals regardless.                                      |
| Nothing arrives at all                                        | Confirm the Appstack app is installed on this Stripe account and connected to your organization (**Integrations** > **Stripe** in Appstack): this metadata step only matters once events are already flowing.                          |
