---
title: "Stripe App"
description: "The Stripe app integration bridges Dub with Stripe accounts, enabling automated Conversion Tracking, bidirectional coupon synchronization, and merchant workspace management. By packaging a Stripe U..."
last_updated: "2026-10-12T07:00:10.142659+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/integrations-subsystem/stripe-app"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/stripe-app/stripe-app.json](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json)
- [packages/stripe-app/src/views/AppSettings.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/integrations/%5BintegrationSlug%5D/page-client.tsx)
- [apps/web/ui/guides/install-stripe-integration-button.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/guides/install-stripe-integration-button.tsx)
- [apps/web/app/ee/api/stripe/integration/webhook/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts)
- [apps/web/lib/discounts/discount-provider-stripe.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-stripe.ts)
- [packages/stripe-app/src/utils/constants.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/constants.ts)
- [apps/web/lib/integrations/stripe/update-stripe-settings.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/update-stripe-settings.ts)
- [apps/web/scripts/dev/data.json](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/dev/data.json)
- [apps/web/app/api/callback/plain/partner/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/callback/plain/partner/route.ts)
- [apps/web/lib/integrations/stripe/ui/settings.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/ui/settings.tsx)
- [apps/web/lib/discounts/discount-provider-shopify.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-shopify.ts)
- [apps/web/app/ee/api/shopify/integration/callback/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/shopify/integration/callback/route.ts)
- [packages/hubspot-app/src/app/app-hsmeta.json](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/hubspot-app/src/app/app-hsmeta.json)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/tracking/installation-section.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/tracking/installation-section.tsx)
- [apps/web/scripts/stripe/backfill-discount-code-sales.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/backfill-discount-code-sales.ts)
- [apps/web/app/ee/api/stripe/integration/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/route.ts)
- [apps/web/app/ee/api/customers/search-stripe/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/customers/search-stripe/route.ts)
- [apps/web/lib/dub.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/dub.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/partners/partnerId/links/discount-codes.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/partners/%5BpartnerId%5D/links/discount-codes.tsx)
- [apps/web/ui/modals/add-customer-modal.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/modals/add-customer-modal.tsx)
- [packages/stripe-app/package.json](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/package.json)
- [apps/web/app/ee/api/stripe/integration/webhook/promotion-code-updated.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/promotion-code-updated.ts)
- [apps/web/scripts/stripe/connect-client.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/connect-client.ts)
- [packages/stripe-app/src/utils/dub.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/dub.ts)
- [apps/web/lib/stripe/index.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/index.ts)
- [packages/stripe-app/src/utils/oauth.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts)
- [packages/stripe-app/src/hooks/use-workspace.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/hooks/use-workspace.ts)
- [packages/hubspot-app/src/app/webhooks/webhooks-hsmeta.json](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/hubspot-app/src/app/webhooks/webhooks-hsmeta.json)
- [apps/web/lib/stripe/coupon-discount-converter.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts)
</details>

## Overview

The Stripe app integration bridges Dub with Stripe accounts, enabling automated [Conversion Tracking](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/analytics-pipeline/conversion-tracking), bidirectional coupon synchronization, and merchant workspace management. By packaging a Stripe UI extension manifest with OAuth capabilities, it allows merchants to link their Dub workspaces directly from the Stripe dashboard, configure tracking preferences, and synchronize promotional discounts. The system securely handles webhook event pipelines for checkout sessions, subscriptions, and invoices, while offering customer lookup tools and historical sales backfill utilities to ensure accurate partner commission attribution.
Sources: [apps/web/scripts/dev/data.json:276-297](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/dev/data.json#L276-L297), [packages/stripe-app/stripe-app.json:1-91](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L1-L91), [packages/stripe-app/src/views/AppSettings.tsx:24-198](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L24-L198), [apps/web/app/ee/api/stripe/integration/webhook/route.ts:22-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L22-L33), [apps/web/lib/discounts/discount-provider-stripe.ts:113-150](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-stripe.ts#L113-L150), [apps/web/lib/integrations/stripe/ui/settings.tsx:22-206](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/ui/settings.tsx#L22-L206), [apps/web/app/ee/api/customers/search-stripe/route.ts:11-91](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/customers/search-stripe/route.ts#L11-L91), [apps/web/scripts/stripe/backfill-discount-code-sales.ts:40-111](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/backfill-discount-code-sales.ts#L40-L111)

## Stripe App Manifest and Extensions

The Dub Stripe app configuration is defined in `packages/stripe-app/stripe-app.json`, identifying the package with ID `dub.co`, version `0.0.25`, and a public distribution type.
Sources: [packages/stripe-app/stripe-app.json:1-21](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L1-L21), [packages/stripe-app/stripe-app.json:88-91](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L88-L91)

The app declares a single settings view viewport mapped to the `AppSettings` component.
Sources: [packages/stripe-app/stripe-app.json:7-13](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L7-L13)

The Content Security Policy within the manifest restricts network connections to Dub's OAuth endpoints and Stripe integration API routes.
Sources: [packages/stripe-app/stripe-app.json:14-21](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L14-L21)

> [!IMPORTANT]
> The `connect-src` policy strictly limits outbound requests to `https://api.dub.co/oauth/` and `https://api.dub.co/stripe/integration`. Any unlisted endpoint calls made from the UI extension will be blocked by Stripe's runtime security container.
> Sources: [packages/stripe-app/stripe-app.json:14-20](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L14-L20)

The Stripe extension package uses React 19, the Stripe UI Extension SDK, and the Stripe Node library. Node version `>=14` is required under engine specifications.
Sources: [packages/stripe-app/package.json:7-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/package.json#L7-L15)

Core connection constants establish the client identifier and host URLs for communicating with the Dub platform from within the extension package.
Sources: [packages/stripe-app/src/utils/constants.ts:1-5](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/constants.ts#L1-L5)

The extension requests specific permission scopes to inspect customer records, subscriptions, invoices, charges, checkout sessions, and webhooks, alongside write capabilities for secrets, coupons, and promotion codes.
Sources: [packages/stripe-app/stripe-app.json:23-80](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L23-L80)

| Permission Scope | Purpose Description |
| :--- | :--- |
| `customer_read` | Allows Dub to read customer information. |
| `subscription_read` | Allows Dub to read subscription information. |
| `invoice_read` | Allows Dub to read invoice information. |
| `charge_read` | Allows Dub to read charges to detect refunded payments. |
| `checkout_session_read` | Allows Dub to read checkout session information. |
| `user_email_read` | Access current user's email address to identify within Dub. |
| `connected_account_read` | Allows reading basic data from connected accounts, such as account name and id. |
| `webhook_read` | Allows Dub to read webhook information. |
| `event_read` | Allows Dub to read event information. |
| `secret_write` | Allows storing Dub access tokens in Stripe for an account. |
| `coupon_write` | Allows Dub to create coupons for an account. |
| `coupon_read` | Allows Dub to read coupons for an account. |
| `promotion_code_write` | Allows Dub to create promotion codes for an account. |
| `promotion_code_read` | Allows Dub to read promotion codes for an account. |

Sources: [packages/stripe-app/stripe-app.json:23-80](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/stripe-app.json#L23-L80)

## Stripe Dashboard OAuth Handshake

```mermaid
sequenceDiagram
    participant App as AppSettings View
    participant Stripe as Stripe OAuth SDK
    participant Dub as Dub API / Authorize
    
    App->>Stripe: createOAuthState()
    Stripe-->>App: Return state & challenge (S256)
    App->>Dub: Redirect to GET /oauth/authorize (client_id, redirect_uri, code_challenge)
    Dub-->>App: Redirect back with auth code & verifier (oauthContext)
    App->>Dub: POST /oauth/token (authorization_code, code_verifier)
    Dub-->>App: Return Token object (access_token, refresh_token)
    App->>Dub: GET /oauth/userinfo (Bearer token)
    Dub-->>App: Return Workspace object
    App->>Dub: PATCH /stripe/integration (stripeAccountId, stripeMode)
```
Sources: [packages/stripe-app/src/views/AppSettings.tsx:30-139](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L30-L139), [packages/stripe-app/src/utils/oauth.ts:11-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L11-L88), [packages/stripe-app/src/utils/dub.ts:5-23](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/dub.ts#L5-L23)

The OAuth authorization handshake begins within the `AppSettings` component when no connected workspace exists in Stripe secrets and no active authorization code is present in the extension context.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:24-141](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L24-L141)

The component invokes `createOAuthState` from `@stripe/ui-extension-sdk/oauth` to generate a cryptographic state string and a PKCE code challenge.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:135-139](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L135-L139), [packages/stripe-app/src/views/AppSettings.tsx:30-31](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L30-L31)

When the merchant clicks the connect action in the `SignInView`, the app builds an authorization URL via `getOAuthUrl`.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:170-183](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L170-L183), [packages/stripe-app/src/utils/oauth.ts:11-30](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L11-L30)

The generated URL points to `${DUB_HOST}/oauth/authorize` and includes query parameters for `client_id`, `redirect_uri`, `response_type` set to `"code"`, `code_challenge`, `code_challenge_method` set to `"S256"`, and the generated `state`.
Sources: [packages/stripe-app/src/utils/oauth.ts:20-29](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L20-L29)

The redirect URI is dynamically calculated based on the Stripe mode using `getRedirectUrl`, pointing to `https://dashboard.stripe.com/apps-oauth/dub.co` for live mode or `https://dashboard.stripe.com/test/apps-oauth/dub.co` for test/sandbox modes.
Sources: [packages/stripe-app/src/utils/oauth.ts:6-8](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L6-L8), [packages/stripe-app/src/utils/oauth.ts:22](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L22)

> [!WARNING]
> If `code` or `verifier` parameters are missing from `oauthContext` upon return from the authorization server, the `connectWorkspace` routine aborts execution immediately without persisting credentials or throwing an unhandled exception.
> Sources: [packages/stripe-app/src/views/AppSettings.tsx:78-80](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L78-L80)

Once the authorization server redirects back to the Stripe app with the authorization code and PKCE verifier (`oauthContext?.code` and `oauthContext?.verifier`), the `useEffect` hook detects the parameters and executes `connectWorkspace`.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:36-37](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L36-L37), [packages/stripe-app/src/views/AppSettings.tsx:127-131](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L127-L131), [packages/stripe-app/src/views/AppSettings.tsx:75-119](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L75-L119)

| Step Function | API Endpoint / Action | Description |
| :--- | :--- | :--- |
| `getToken` | `POST /oauth/token` | Exchanges the authorization code and PKCE `code_verifier` for access and refresh tokens using `application/x-www-form-urlencoded`. |
| `setSecret` (`dub_token`) | Stripe Secrets API | Stores the serialized token JSON payload securely within Stripe extension secrets. |
| `getUserInfo` | `GET /oauth/userinfo` | Fetches the user profile and associated workspace metadata from Dub using the `Bearer` access token. |
| `updateWorkspace` | `PATCH /stripe/integration` | Links the Dub workspace to the merchant's Stripe account by sending `stripeAccountId` and `stripeMode`. |
| `setSecret` (`dub_workspace`) | Stripe Secrets API | Stores the retrieved workspace metadata object securely as `dub_workspace`. |

Sources: [packages/stripe-app/src/views/AppSettings.tsx:82-114](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L82-L114), [packages/stripe-app/src/utils/dub.ts:14-23](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/dub.ts#L14-L23), [packages/stripe-app/src/utils/oauth.ts:43-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L43-L88)

> [!TIP]
> The `stripeMode` sent to `updateWorkspace` evaluates `userContext.account.isSandbox`. If the Stripe account is in sandbox mode, it resolves to `"sandbox"`; otherwise, it falls back to `environment.mode`.
> Sources: [packages/stripe-app/src/views/AppSettings.tsx:61-63](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L61-L63), [packages/stripe-app/src/views/AppSettings.tsx:106-108](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L106-L108)

To ensure continuous background operation without re-prompting merchants, `getValidToken` inspects stored tokens via `getSecret` for the `dub_token` key.
Sources: [packages/stripe-app/src/utils/oauth.ts:91-95](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L91-L95)

It validates the active token by calling `getUserInfo`. If the request fails due to expiration or revocation, it invokes `refreshToken` against `${DUB_API_HOST}/oauth/token` using the stored `refresh_token` and `grant_type: "refresh_token"`.
Sources: [packages/stripe-app/src/utils/oauth.ts:101-105](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L101-L105), [packages/stripe-app/src/utils/oauth.ts:123-137](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L123-L137)

If refreshing succeeds, the new token payload is persisted back to Stripe secrets under `dub_token` and returned to the caller. If refresh fails, `getValidToken` logs an error and returns `null`.
Sources: [packages/stripe-app/src/utils/oauth.ts:111-118](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L111-L118), [packages/stripe-app/src/utils/oauth.ts:106-109](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/utils/oauth.ts#L106-L109)

When a merchant elects to disconnect their workspace from the `AppSettings` banner view, `disconnectWorkspace` triggers.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:40-41](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L40-L41), [packages/stripe-app/src/views/AppSettings.tsx:154-167](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L154-L167)

It retrieves a valid token, deletes both `dub_workspace` and `dub_token` secrets from Stripe in parallel using `Promise.all`, and issues an asynchronous `updateWorkspace` patch request with `accountId: null` to disassociate the Stripe account on the Dub backend.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:43-65](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L43-L65)

Finally, it invokes `mutate()` to refresh workspace hook state.
Sources: [packages/stripe-app/src/views/AppSettings.tsx:67](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L67), [packages/stripe-app/src/views/AppSettings.tsx:161](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/stripe-app/src/views/AppSettings.tsx#L161)

## Integration Settings and Workspace Management

The Dub dashboard exposes configuration interfaces and server actions for managing Stripe integration settings per workspace.
Sources: [apps/web/lib/integrations/stripe/update-stripe-settings.ts:21-60](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/update-stripe-settings.ts#L21-L60), [apps/web/lib/integrations/stripe/ui/settings.tsx:22-206](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/ui/settings.tsx#L22-L206)

The client interface is rendered via `IntegrationPageClient`, which resolves the correct settings component (`StripeIntegrationSettings`) using the `STRIPE_INTEGRATION_ID` constant.
Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx:72](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/integrations/%5BintegrationSlug%5D/page-client.tsx#L72), [apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx:121](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/integrations/%5BintegrationSlug%5D/page-client.tsx#L121)

When an integration is successfully connected, a dynamic connection banner displays the current operational mode: `live` (emerald background), `test` (blue background), or `sandbox` (orange background).
Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx:123-158](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/integrations/%5BintegrationSlug%5D/page-client.tsx#L123-L158)

| Mode Variant | Background Class | Title Text | Icon Component |
| :--- | :--- | :--- | :--- |
| `live` | `bg-emerald-50` | Live Mode | `CheckCircleFill` |
| `test` | `bg-[#F0F7FF]` | Test Mode | `Flask` |
| `sandbox` | `bg-orange-50` | Sandbox Mode | `Flask` |

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx:133-154](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/integrations/%5BintegrationSlug%5D/page-client.tsx#L133-L154)

Workspaces requiring the integration can initialize installation via `InstallStripeIntegrationButton`.
Sources: [apps/web/ui/guides/install-stripe-integration-button.tsx:13](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/guides/install-stripe-integration-button.tsx#L13)

This component monitors active integrations using `useIntegrations` with a tailored polling interval (`5000`ms normally, falling back to `1000`ms when clicked).
Sources: [apps/web/ui/guides/install-stripe-integration-button.tsx:19-24](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/guides/install-stripe-integration-button.tsx#L19-L24)

If the Stripe integration is not detected, it renders a required setup callout and a primary link pointing to `/[workspaceSlug]/settings/integrations/stripe`.
Sources: [apps/web/ui/guides/install-stripe-integration-button.tsx:47-69](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/guides/install-stripe-integration-button.tsx#L47-L69)

> [!NOTE]
> The installation check hook continuously revalidates on focus unless `isEnabled` resolves to true. When clicked, it accelerates polling to every 1,000 milliseconds to promptly capture installation status updates.
> Sources: [apps/web/ui/guides/install-stripe-integration-button.tsx:19-24](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/guides/install-stripe-integration-button.tsx#L19-L24)

Workspace settings updates are defined in `apps/web/lib/integrations/stripe/update-stripe-settings.ts`, wrapped with `authActionClient`.
Sources: [apps/web/lib/integrations/stripe/update-stripe-settings.ts:21-23](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/update-stripe-settings.ts#L21-L23)

The action validates inputs against a schema requiring `workspaceId`, a `freeTrials` configuration object containing boolean toggles for `enabled` and `trackQuantity`, and an optional `discountCodeRestrictions` object containing `firstTimeTransaction`.
Sources: [apps/web/lib/integrations/stripe/update-stripe-settings.ts:11-25](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/update-stripe-settings.ts#L11-L25)

It verifies that the user possesses `integrations.write` permissions via `throwIfNoPermission`, locates the installed integration record in Prisma, merges the settings payload, and calls `revalidatePath`.
Sources: [apps/web/lib/integrations/stripe/update-stripe-settings.ts:27-60](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/integrations/stripe/update-stripe-settings.ts#L27-L60)

Additionally, the backend exposes a `PATCH /api/stripe/integration` route restricted to business, advanced, and enterprise plans with owner or member roles.
Sources: [apps/web/app/ee/api/stripe/integration/route.ts:18-19](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/route.ts#L18-L19), [apps/web/app/ee/api/stripe/integration/route.ts:110-112](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/route.ts#L110-L112)

This endpoint parses request bodies for `stripeAccountId` and `stripeMode`, validates the token's `installationId`, updates the project's `stripeConnectId`, and uses Vercel's `waitUntil` helper to either delete the installation if `stripeAccountId` is `null` or update the installation settings with the new `stripeMode`.
Sources: [apps/web/app/ee/api/stripe/integration/route.ts:20-89](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/route.ts#L20-L89)

> [!WARNING]
> If a `P2002` unique constraint violation occurs during the project update (indicating the Stripe Connect account is already registered elsewhere), the API route throws a Dub API conflict error.
> Sources: [apps/web/app/(ee)/api/stripe/integration/route.ts:95-101](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/route.ts#L95-L101)

## Discount and Coupon Synchronization

Dub maps partner discount attributes between Dub and Stripe through dedicated converter utilities.
Sources: [apps/web/lib/stripe/coupon-discount-converter.ts:17-47](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts#L17-L47), [apps/web/lib/stripe/coupon-discount-converter.ts:52-83](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts#L52-L83)

The conversion handles percentage versus flat monetary amounts, currency constraints, and duration mappings.
Sources: [apps/web/lib/stripe/coupon-discount-converter.ts:20-44](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts#L20-L44), [apps/web/lib/stripe/coupon-discount-converter.ts:56-74](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts#L56-L74)

| Dub Max Duration | Stripe Coupon Duration | Duration In Months |
| :--- | :--- | :--- |
| `null` | `forever` | *undefined* |
| `0` | `once` | *undefined* |
| Positive integer ($N$) | `repeating` | $N$ |

Sources: [apps/web/lib/stripe/coupon-discount-converter.ts:24-31](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts#L24-L31)

> [!CAUTION]
> Amount-based flat coupons (`amount_off`) require `currency: "usd"` in Stripe. If a coupon specifies both `percent_off` and `amount_off`, or omits both entirely, validation fails.
> Sources: [apps/web/lib/stripe/coupon-discount-converter.ts:93-102](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/stripe/coupon-discount-converter.ts#L93-L102)

When creating discount codes for partner links via `createDiscountCode`, Dub connects to the Stripe API using the workspace's active mode and `stripeConnectId`.
Sources: [apps/web/lib/discounts/discount-provider-stripe.ts:152-167](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-stripe.ts#L152-L167)

It enforces first-time transaction restrictions configured in the workspace settings.
Sources: [apps/web/lib/discounts/discount-provider-stripe.ts:188-191](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-stripe.ts#L188-L191)

> [!TIP]
> If a generated discount code already exists in Stripe and `shouldRetry` is enabled, Dub automatically appends a 2-character nanoid suffix (`${currentCode}${nanoid(2)}`) and retries up to `MAX_ATTEMPTS` (3 times) before throwing a creation failure error.
> Sources: [apps/web/lib/discounts/discount-provider-stripe.ts:17-21](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-stripe.ts#L17-L21), [apps/web/lib/discounts/discount-provider-stripe.ts:199-218](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/discounts/discount-provider-stripe.ts#L199-L218)

Stripe promotion code updates are intercepted via the `promotion_code.updated` webhook handler.
Sources: [apps/web/app/ee/api/stripe/integration/webhook/promotion-code-updated.ts:6-14](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/promotion-code-updated.ts#L6-L14)

If a promotion code becomes inactive (`promotionCode.active` is false), the system looks up the corresponding record in the workspace's default program and removes it.
Sources: [apps/web/app/ee/api/stripe/integration/webhook/promotion-code-updated.ts:22-48](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/promotion-code-updated.ts#L22-L48)

## Merchant Webhook Processing Pipeline

The Stripe integration webhook endpoint (`POST /api/stripe/integration/webhook`) accepts incoming event notifications, verifies cryptographic signatures, and matches requests against the appropriate environment secret.
Sources: [apps/web/app/ee/api/stripe/integration/webhook/route.ts:36-41](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L36-L41), [apps/web/app/ee/api/stripe/integration/webhook/route.ts:64-70](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L64-L70)

Depending on the request path suffix, the webhook router selects either `STRIPE_APP_WEBHOOK_SECRET_TEST` (`/test`), `STRIPE_APP_WEBHOOK_SECRET_SANDBOX` (`/sandbox`), or the production `STRIPE_APP_WEBHOOK_SECRET` (`live`).
Sources: [apps/web/app/ee/api/stripe/integration/webhook/route.ts:46-55](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L46-L55)

> [!CAUTION]
> If a test webhook event is received on the live webhook receiver endpoint (`!event.livemode && mode === "live"`), the event is explicitly dropped to prevent cross-mode pollution.
> Sources: [apps/web/app/(ee)/api/stripe/integration/webhook/route.ts:91-97](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L91-L97)

Dub tracks a specific subset of Stripe lifecycle events through `relevantEvents`, resolving the associated workspace via the event's `stripeConnectId` (`event.account`).
Sources: [apps/web/app/ee/api/stripe/integration/webhook/route.ts:22-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L22-L33), [apps/web/app/ee/api/stripe/integration/webhook/route.ts:107-118](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L107-L118)

Events failing to match workspace records or unsupported types are skipped and acknowledged.
Sources: [apps/web/app/ee/api/stripe/integration/webhook/route.ts:79-85](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L79-L85), [apps/web/app/ee/api/stripe/integration/webhook/route.ts:120-125](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L120-L125)

| Event Type | Handler / Action |
| :--- | :--- |
| `account.application.deauthorized` | `accountApplicationDeauthorized` |
| `charge.refunded` | `chargeRefunded` |
| `checkout.session.completed` | `checkoutSessionCompleted` |
| `coupon.deleted` | `couponDeleted` |
| `customer.created` | `syncCustomer` |
| `customer.updated` | `syncCustomer` |
| `customer.subscription.created` | `customerSubscriptionCreated` |
| `customer.subscription.deleted` | `customerSubscriptionDeleted` |
| `invoice.paid` | `invoicePaid` |
| `promotion_code.updated` | `promotionCodeUpdated` |

Sources: [apps/web/app/ee/api/stripe/integration/webhook/route.ts:22-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L22-L33), [apps/web/app/ee/api/stripe/integration/webhook/route.ts:133-189](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L133-L189)

> [!TIP]
> If event execution throws a Stripe rate limit error (`isStripeRateLimitError`), the webhook handler catches it and returns HTTP status `429` with a warning log level so Stripe automatically retries the delivery instead of treating it as an internal server error (`500`).
> Sources: [apps/web/app/(ee)/api/stripe/integration/webhook/route.ts:191-204](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/stripe/integration/webhook/route.ts#L191-L204)

## Customer Lookup and Historical Backfills

Workspace owners can query connected Stripe accounts by email directly from the Dub dashboard using the customer search endpoint (`GET /api/customers/search-stripe`).
Sources: [apps/web/app/ee/api/customers/search-stripe/route.ts:11-16](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/customers/search-stripe/route.ts#L11-L16)

The endpoint first validates that the workspace has an active Stripe connection ID and an installed Stripe integration containing valid settings.
Sources: [apps/web/app/ee/api/customers/search-stripe/route.ts:18-47](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/customers/search-stripe/route.ts#L18-L47)

Once the integration settings are verified, the server queries the connected Stripe account using the configured Stripe mode and maps matching customer records against the local database to ascertain whether customers already exist within Dub.
Sources: [apps/web/app/ee/api/customers/search-stripe/route.ts:49-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/customers/search-stripe/route.ts#L49-L88)

> [!WARNING]
> If the Stripe integration is not installed or the workspace lacks a connected Stripe account, the search endpoint throws a `bad_request` API error halting the customer search operation.
> Sources: [apps/web/app/(ee)/api/customers/search-stripe/route.ts:18-43](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/customers/search-stripe/route.ts#L18-L43)

The add customer modal interface provides a streamlined email lookup tool allowing merchants to search for customers inside their connected Stripe accounts.
Sources: [apps/web/ui/modals/add-customer-modal.tsx:251-291](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/modals/add-customer-modal.tsx#L251-L291)

When a valid email address is entered, the interface calls the search handler, displaying matching records alongside their Stripe identifier, subscription counts, and whether they are already registered on Dub.
Sources: [apps/web/ui/modals/add-customer-modal.tsx:229-234](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/modals/add-customer-modal.tsx#L229-L234), [apps/web/ui/modals/add-customer-modal.tsx:298-369](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/modals/add-customer-modal.tsx#L298-L369)

To recover partner sales and commissions skipped by webhooks prior to patch `#4609`—where matching checks failed when an external ID resolved to no Dub customer—administrators execute the historical backfill script (`apps/web/scripts/stripe/backfill-discount-code-sales.ts`).
Sources: [apps/web/scripts/stripe/backfill-discount-code-sales.ts:12-22](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/backfill-discount-code-sales.ts#L12-L22), [apps/web/scripts/stripe/backfill-discount-code-sales.ts:40-59](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/backfill-discount-code-sales.ts#L40-L59)

The script iterates through paid invoices created on or after `START_DATE`, verifying promotion codes against valid partner discount records, and verifies subscription creation dates before creating manual commissions.
Sources: [apps/web/scripts/stripe/backfill-discount-code-sales.ts:130-156](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/backfill-discount-code-sales.ts#L130-L156), [apps/web/scripts/stripe/backfill-discount-code-sales.ts:193-207](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/scripts/stripe/backfill-discount-code-sales.ts#L193-L207)

## Related

- [Workspace Subscriptions](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/billing-and-payouts/workspace-subscriptions)
- [Conversion Tracking](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/analytics-pipeline/conversion-tracking)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/llms.txt) for all pages in this wiki.
