Getting Started
Core Architecture
Link Engine
Analytics & Attribution
Partners & Affiliates
Third-Party Integrations
Identity & Security
Automation & Messaging
Developer Tools
The following files were used as context for generating this wiki page:
The Stripe Marketplace App integration bridges Stripe payments and Dub's partner program infrastructure, automating conversion tracking and partner commission generation. By packaging extension definitions, secure OAuth authentication flows, workspace context hooks, and native settings controls into a unified Stripe App manifest, it enables merchants to seamlessly connect their Stripe accounts with Dub workspaces. This integration solves the complexity of attribution across payment boundaries, ensuring that checkout events, invoices, and subscriptions correctly trigger affiliate rewards without manual reconciliation. Sources: packages/stripe-app/stripe-app.json:1-90, apps/web/scripts/dev/data.json:277-297, packages/stripe-app/src/views/AppSettings.tsx:1-200
The Stripe App manifest configuration defines the structural integration between the Dub Partners application and the Stripe Marketplace ecosystem. Controlled by packages/stripe-app/stripe-app.json against the official Stripe App schema, the application identifies as dub.co under the display name Dub Partners at version 0.0.25. Distribution is configured as public with sandbox installation compatibility enabled, utilizing OAuth as the Stripe API access type. Sources: packages/stripe-app/stripe-app.json:1-91
The UI extension block establishes the view mount points and security policies required for rendering settings controls inside the Stripe dashboard. A single view is declared for the settings viewport, loading the AppSettings component. Content security policy settings explicitly whitelist OAuth and integration endpoints for connection requests. Sources: packages/stripe-app/stripe-app.json:7-21
Note
The content security policy restricts connect-src directives strictly to https://api.dub.co/oauth/ and https://api.dub.co/stripe/integration, ensuring all external API calls remain bound to authorized Dub domains. Sources: packages/stripe-app/stripe-app.json:14-20
The manifest requires an extensive permissions array covering customer, subscription, invoice, charge, checkout session, user email, connected account, webhook, event, secret, coupon, and promotion code scopes. Alongside these permissions, the app configures allowed redirect URIs and post-install actions. Client configuration constants establish connection endpoints and the Dub client identifier used across runtime integrations. Sources: packages/stripe-app/stripe-app.json:23-90, packages/stripe-app/src/utils/constants.ts:1-6
Caution
Modifying allowed_redirect_uris or altering the connect-src CSP block without updating the corresponding backend routes at https://app.dub.co/api/stripe/integration/callback will break the OAuth authorization exchange. Sources: packages/stripe-app/stripe-app.json:14-20, packages/stripe-app/stripe-app.json:85-87
The OAuth handshake and authentication lifecycle manages user authorization between the Stripe App and the Dub platform. This subsystem initializes cryptographic state parameters, exchanges authorization codes for authentication tokens, and validates active sessions against the Dub API. Sources: packages/stripe-app/src/views/AppSettings.tsx:24-142
When an unauthenticated user loads the application settings view without an active workspace, the runtime executes a precise initialization and authentication sequence. Sources: packages/stripe-app/src/views/AppSettings.tsx:121-141
useEffect evaluates the initial component state: if workspace is absent and no code or verifier parameters are present in the oauthContext, it invokes createOAuthState() from @stripe/ui-extension-sdk/oauth. Sources: packages/stripe-app/src/views/AppSettings.tsx:121-139createOAuthState() resolves with an state string and a cryptographic challenge, which are stored via setOAuthState and setChallenge. Sources: packages/stripe-app/src/views/AppSettings.tsx:30-31, packages/stripe-app/src/views/AppSettings.tsx:135-138SignInView component, whose primary action triggers getOAuthUrl({ state, challenge, mode }) using the generated parameters. Sources: packages/stripe-app/src/views/AppSettings.tsx:170-183oauthContext?.code and oauthContext?.verifier, triggering the connectWorkspace() function. Sources: packages/stripe-app/src/views/AppSettings.tsx:36-37, packages/stripe-app/src/views/AppSettings.tsx:127-131connectWorkspace() calls getToken({ code, verifier, mode }) to exchange the authorization code for an access token. Sources: packages/stripe-app/src/views/AppSettings.tsx:82-86setSecret({ stripe, name: "dub_token", payload }). Sources: packages/stripe-app/src/views/AppSettings.tsx:92-96getUserInfo({ token }) fetches associated workspace details, which are then stored via setSecret({ stripe, name: "dub_workspace", payload }) after calling updateWorkspace(). Sources: packages/stripe-app/src/views/AppSettings.tsx:98-114Warning
If code or verifier are missing when the effect hook fires, connectWorkspace() exits immediately without executing token exchange, halting the handshake. Sources: packages/stripe-app/src/views/AppSettings.tsx:78-80
The authentication flow relies on predefined constants pointing to the Dub platform infrastructure. Sources: packages/stripe-app/src/utils/constants.ts:1-6
Tip
The credentialsUsed ref prevents duplicate execution of connectWorkspace() if component re-renders coincide with an active OAuth callback lifecycle. Sources: packages/stripe-app/src/views/AppSettings.tsx:29](https://github.com/blade47/dub/blob/HEAD/packages/stripe-app/src/views/AppSettings.tsx#L29), [packages/stripe-app/src/views/AppSettings.tsx:117, packages/stripe-app/src/views/AppSettings.tsx:128`
The useWorkspace hook manages workspace retrieval and synchronization within the Stripe UI extension context. It accepts a Stripe instance, maintains local loading and workspace states via React hooks, and exposes a mutation handle to reload workspace data on demand. Sources: packages/stripe-app/src/hooks/use-workspace.ts:7-27
export const useWorkspace = (stripe: Stripe) => {
const [workspace, setWorkspace] = useState<Workspace | null>(null);
const [isLoading, setIsLoading] = useState(true);
const loadWorkspace = useCallback(async () => {
setIsLoading(true);
const fetchedWorkspace = await fetchWorkspace({ stripe });
setWorkspace(fetchedWorkspace);
setIsLoading(false);
}, [stripe]);
useEffect(() => {
loadWorkspace();
}, [loadWorkspace]);
return {
workspace,
isLoading,
mutate: loadWorkspace,
};
};Workspace persistence relies on Stripe's secret storage layer. The internal fetchWorkspace helper queries secret storage for the dub_workspace entry using the provided stripe client instance, returning the deserialized Workspace object or null if unconfigured. Sources: packages/stripe-app/src/hooks/use-workspace.ts:29-37
async function fetchWorkspace({ stripe }: { stripe: Stripe }) {
const workspace = await getSecret<Workspace>({
stripe,
name: "dub_workspace",
});
return workspace;
}Note
The useWorkspace hook automatically triggers loadWorkspace() on initial component mount through a useEffect hook wrapped around useCallback. Sources: packages/stripe-app/src/hooks/use-workspace.ts:11-20
Views such as AppSettings receive extension context via the ExtensionContextValue interface provided by @stripe/ui-extension-sdk/context. This context supplies runtime properties including userContext, oauthContext, and environment configuration, alongside the initialized stripe SDK utility. Sources: packages/stripe-app/src/views/AppSettings.tsx:1-28
The AppSettings component manages the extension settings user interface, governing workspace connectivity, OAuth callback handling, and credentials lifecycle. Built with Stripe UI extension components such as SignInView, Banner, Box, and Spinner, the view dynamically renders either an active workspace banner with a destructive disconnect control or a sign-in view prompting users to link their Dub account. Sources: packages/stripe-app/src/views/AppSettings.tsx:4-10, packages/stripe-app/src/views/AppSettings.tsx:24-35, packages/stripe-app/src/views/AppSettings.tsx:147-195
Note
When isLoading or connecting is true, AppSettings immediately renders a centered Spinner component with a large size, blocking UI interaction during asynchronous token exchanges and workspace lookups. Sources: packages/stripe-app/src/views/AppSettings.tsx:143-145
When an authorization code returns from the OAuth redirect, the component automatically executes connectWorkspace() inside a useEffect hook. The exact call chain proceeds as follows:
getToken() — Exchanges the authorization code and verifier for an API token under the active environment mode. Sources: packages/stripe-app/src/views/AppSettings.tsx:82-86setSecret() — Persists the serialized token payload into Stripe secret storage under the name dub_token. Sources: packages/stripe-app/src/views/AppSettings.tsx:92-96getUserInfo() — Queries Dub workspace metadata using the acquired token. Sources: packages/stripe-app/src/views/AppSettings.tsx:98`updateWorkspace() — Links the Dub account to the Stripe account ID, resolving stripe mode between sandbox and live execution based on userContext.account.isSandbox. Sources: packages/stripe-app/src/views/AppSettings.tsx:104-108setSecret() — Stores the retrieved workspace metadata in secret storage under dub_workspace. Sources: packages/stripe-app/src/views/AppSettings.tsx:110-114mutate() — Refreshes the local workspace hook state, concluding with credentialsUsed.current = true. Sources: packages/stripe-app/src/views/AppSettings.tsx:116-117 const connectWorkspace = async () => {
setConnecting(true);
if (!code || !verifier) {
return;
}
const token = await getToken({
code,
verifier,
mode: environment.mode,
});
if (!token) {
return;
}
await setSecret({
stripe,
name: "dub_token",
payload: JSON.stringify(token),
});
const workspace = await getUserInfo({ token });
if (!workspace) {
return;
}
await updateWorkspace({
token,
accountId: userContext.account.id,
stripeMode: userContext.account.isSandbox ? "sandbox" : environment.mode,
});
await setSecret({
stripe,
name: "dub_workspace",
payload: JSON.stringify(workspace),
});
await mutate();
credentialsUsed.current = true;
setConnecting(false);
};Disconnecting a workspace reverses the linkage via the disconnectWorkspace routine. It invokes getValidToken() to authenticate the removal request, deletes both secret keys (dub_workspace and dub_token) in parallel using Promise.all(), updates the remote workspace with a null account ID, and triggers a state mutation. Sources: packages/stripe-app/src/views/AppSettings.tsx:40-69
Warning
The useEffect dependency array monitors [workspace, oauthState, code, verifier]. If code and verifier are present without an existing workspace and credentialsUsed.current is false, it executes connectWorkspace() exactly once, preventing duplicate authorization code exchanges. Sources: packages/stripe-app/src/views/AppSettings.tsx:121-141
The Dub web dashboard provides integration management interfaces and UI triggers for installing the Stripe marketplace app. Within a workspace's settings, the dashboard maps registered integrations to their respective settings components, utilizing STRIPE_INTEGRATION_ID to render Stripe-specific UI elements. Sources: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx:67-75
For installed Stripe integrations, the client evaluates settings against stripeIntegrationSettingsSchema to determine the execution mode. Based on the parsed mode, it configures visual status banners distinguishing between live, test, and sandbox environments. Sources: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx:123-158
Note
The uninstallation link dynamically adjusts its URL based on the active Stripe mode, directing users to the appropriate test or live app installation management path in the Stripe Dashboard. Sources: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx:160-164
The installation trigger uses InstallStripeIntegrationButton to poll active workspace integrations and manage installation state transitions. If the Stripe integration is not yet active, it renders a call-to-action button linking to the workspace integration settings page while polling state every 1000ms to 5000ms. Sources: apps/web/ui/guides/install-stripe-integration-button.tsx:13-31, apps/web/ui/guides/install-stripe-integration-button.tsx:52-71