Skip to main content
doc0blade47/dubdocumentation home
Sign in

Stripe App

Last updated Oct 12, 2026100% grounded
Relevant source files

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

Overview

The Stripe app integration bridges Dub with Stripe accounts, enabling automated 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, packages/stripe-app/stripe-app.json:1-91, packages/stripe-app/src/views/AppSettings.tsx:24-198, apps/web/app/ee/api/stripe/integration/webhook/route.ts:22-33, apps/web/lib/discounts/discount-provider-stripe.ts:113-150, apps/web/lib/integrations/stripe/ui/settings.tsx:22-206, apps/web/app/ee/api/customers/search-stripe/route.ts:11-91, apps/web/scripts/stripe/backfill-discount-code-sales.ts:40-111

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, packages/stripe-app/stripe-app.json:88-91

The app declares a single settings view viewport mapped to the AppSettings component. Sources: packages/stripe-app/stripe-app.json:7-13

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

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

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

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

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

Permission ScopePurpose Description
customer_readAllows Dub to read customer information.
subscription_readAllows Dub to read subscription information.
invoice_readAllows Dub to read invoice information.
charge_readAllows Dub to read charges to detect refunded payments.
checkout_session_readAllows Dub to read checkout session information.
user_email_readAccess current user's email address to identify within Dub.
connected_account_readAllows reading basic data from connected accounts, such as account name and id.
webhook_readAllows Dub to read webhook information.
event_readAllows Dub to read event information.
secret_writeAllows storing Dub access tokens in Stripe for an account.
coupon_writeAllows Dub to create coupons for an account.
coupon_readAllows Dub to read coupons for an account.
promotion_code_writeAllows Dub to create promotion codes for an account.
promotion_code_readAllows Dub to read promotion codes for an account.

Sources: packages/stripe-app/stripe-app.json:23-80

Stripe Dashboard OAuth Handshake

Sources: packages/stripe-app/src/views/AppSettings.tsx:30-139, packages/stripe-app/src/utils/oauth.ts:11-88, packages/stripe-app/src/utils/dub.ts:5-23

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

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, packages/stripe-app/src/views/AppSettings.tsx:30-31

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, packages/stripe-app/src/utils/oauth.ts:11-30

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

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, packages/stripe-app/src/utils/oauth.ts:22

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

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, packages/stripe-app/src/views/AppSettings.tsx:127-131, packages/stripe-app/src/views/AppSettings.tsx:75-119

Step FunctionAPI Endpoint / ActionDescription
getTokenPOST /oauth/tokenExchanges the authorization code and PKCE code_verifier for access and refresh tokens using application/x-www-form-urlencoded.
setSecret (dub_token)Stripe Secrets APIStores the serialized token JSON payload securely within Stripe extension secrets.
getUserInfoGET /oauth/userinfoFetches the user profile and associated workspace metadata from Dub using the Bearer access token.
updateWorkspacePATCH /stripe/integrationLinks the Dub workspace to the merchant's Stripe account by sending stripeAccountId and stripeMode.
setSecret (dub_workspace)Stripe Secrets APIStores the retrieved workspace metadata object securely as dub_workspace.

Sources: packages/stripe-app/src/views/AppSettings.tsx:82-114, packages/stripe-app/src/utils/dub.ts:14-23, packages/stripe-app/src/utils/oauth.ts:43-88

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, packages/stripe-app/src/views/AppSettings.tsx:106-108

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

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, packages/stripe-app/src/utils/oauth.ts:123-137

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, packages/stripe-app/src/utils/oauth.ts:106-109

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, packages/stripe-app/src/views/AppSettings.tsx:154-167

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

Finally, it invokes mutate() to refresh workspace hook state. Sources: packages/stripe-app/src/views/AppSettings.tsx:67, packages/stripe-app/src/views/AppSettings.tsx:161

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, apps/web/lib/integrations/stripe/ui/settings.tsx:22-206

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, apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx:121

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

Mode VariantBackground ClassTitle TextIcon Component
livebg-emerald-50Live ModeCheckCircleFill
testbg-[#F0F7FF]Test ModeFlask
sandboxbg-orange-50Sandbox ModeFlask

Sources: apps/web/app/app.dub.co/dashboard/slug/ee/settings/integrations/integrationSlug/page-client.tsx:133-154

Workspaces requiring the integration can initialize installation via InstallStripeIntegrationButton. Sources: apps/web/ui/guides/install-stripe-integration-button.tsx:13

This component monitors active integrations using useIntegrations with a tailored polling interval (5000ms normally, falling back to 1000ms when clicked). Sources: apps/web/ui/guides/install-stripe-integration-button.tsx:19-24

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

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

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

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

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

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, apps/web/app/ee/api/stripe/integration/route.ts:110-112

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

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

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, apps/web/lib/stripe/coupon-discount-converter.ts:52-83

The conversion handles percentage versus flat monetary amounts, currency constraints, and duration mappings. Sources: apps/web/lib/stripe/coupon-discount-converter.ts:20-44, apps/web/lib/stripe/coupon-discount-converter.ts:56-74

Dub Max DurationStripe Coupon DurationDuration In Months
nullforeverundefined
0onceundefined
Positive integer ($N$)repeating$N$

Sources: apps/web/lib/stripe/coupon-discount-converter.ts:24-31

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

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

It enforces first-time transaction restrictions configured in the workspace settings. Sources: apps/web/lib/discounts/discount-provider-stripe.ts:188-191

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, apps/web/lib/discounts/discount-provider-stripe.ts:199-218

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

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

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, apps/web/app/ee/api/stripe/integration/webhook/route.ts:64-70

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

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

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, apps/web/app/ee/api/stripe/integration/webhook/route.ts:107-118

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, apps/web/app/ee/api/stripe/integration/webhook/route.ts:120-125

Event TypeHandler / Action
account.application.deauthorizedaccountApplicationDeauthorized
charge.refundedchargeRefunded
checkout.session.completedcheckoutSessionCompleted
coupon.deletedcouponDeleted
customer.createdsyncCustomer
customer.updatedsyncCustomer
customer.subscription.createdcustomerSubscriptionCreated
customer.subscription.deletedcustomerSubscriptionDeleted
invoice.paidinvoicePaid
promotion_code.updatedpromotionCodeUpdated

Sources: apps/web/app/ee/api/stripe/integration/webhook/route.ts:22-33, apps/web/app/ee/api/stripe/integration/webhook/route.ts:133-189

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

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

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

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

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

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

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, apps/web/ui/modals/add-customer-modal.tsx:298-369

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, apps/web/scripts/stripe/backfill-discount-code-sales.ts:40-59

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, apps/web/scripts/stripe/backfill-discount-code-sales.ts:193-207

Was this page helpful?