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 partner portal and onboarding subsystem on partners.dub.co provides a comprehensive, multi-tenant environment where affiliates and referrers can apply to programs, configure custom tracking and payout channels, and monitor their performance. It manages session security, handles automated profile onboarding workflows, generates optimized referral and discount links, and embeds performance metrics directly into partner workflows.
The request handling on partners.dub.co is driven by an edge middleware that inspects inbound requests, resolves token-based user sessions, enforces authentication policies across sensitive routes, and manages partner onboarding redirections. When requests arrive, path parsing extracts the target route, search parameters, and full path context before executing authorization checks against predefined path groupings.
The PartnersMiddleware function executes a multi-step evaluation sequence for every request hitting the partners domain:
parse(req) — Extracts path, fullPath, searchParamsObj, and searchParamsString from the inbound request.getUserViaToken(req) — Resolves the user session via authentication tokens.partnersMarketplaceRedirects(path, searchParamsObj) — Checks legacy marketplace paths and issues a 301 permanent redirect if a match is found.partnersProgramRedirects(path) — Evaluates legacy program redirect rules.isAuthenticatedPath is true, unauthenticated users attempting to access /programs/ paths are redirected to /${programSlug}/login, while other protected routes redirect to /login?next=... with open-redirect protection via isValidInternalRedirect.Warning
When handling the ?next= query parameter, isValidInternalRedirect must validate the target path against the current request URL to prevent open redirect vulnerabilities, excluding /onboarding paths to guarantee proper enrollment completion.
Sources: apps/web/lib/middleware/partners.ts:91-102
The middleware enforces authentication across a strict set of application routes. Requests targeting any path starting with these prefixes require a valid user session and an associated partner profile ID.
Note
Authenticated users who lack a defaultPartnerId and are not currently visiting /onboarding, /account, or an invite route (/invite) are automatically redirected to /onboarding to complete their profile setup.
Sources: apps/web/lib/middleware/partners.ts:75-89
Client-side rendering routes use specialized wrapper components such as PartnerProfileAuth and AcceptPartnerInvitePage to verify SWR session states and handle profile error codes.
export function PartnerProfileAuth({ children }: { children: ReactNode }) {
const searchParams = useSearchParams();
const { loading: sessionLoading } = useRefreshSession("defaultPartnerId");
const { partner, error } = usePartnerProfile();
useEffect(() => {
const error = searchParams?.get("error");
if (error) {
toast.error(ERROR_CODES[error] || error);
}
}, [searchParams]);
const loading = sessionLoading || (!partner && !error);
if (loading) {
return <LayoutLoader />;
}
if (!loading && error && error.status === 404) {
redirect("/onboarding");
}
return children;
}The partner profile authentication layer recognizes specific error codes when connecting external payout channels or checking authorization status.
Partner program application pages and onboarding layouts govern the entry point for prospective affiliates on partners.dub.co. The system handles dynamic program slugs, static parameter generation, and multi-step welcome sequences via React Email templates.
Sources: apps/web/app/ee/partners.dub.co/apply/programSlug/default/layout.tsx:1-110, packages/email/src/templates/welcome-email-partner.tsx:1-123
The application layout for dynamic program slugs resolves partner group data and constructs page metadata, falling back to default partner groups when no group slug is provided.
export async function generateMetadata(props: {
params: Promise<{ programSlug: string; groupSlug?: string }>;
}) {
const { programSlug, groupSlug } = await props.params;
const partnerGroupSlug = groupSlug ?? DEFAULT_PARTNER_GROUP.slug;
const program = await getProgram({
slug: programSlug,
groupSlug: partnerGroupSlug,
});
if (!program) {
notFound();
}
return constructMetadata({
title: `${program.name} Affiliate Program`,
description: `Join the ${program.name} affiliate program and ${
program.rewards && program.rewards.length > 0
? formatRewardDescription(program.rewards[0]).toLowerCase()
: "earn commissions"
} by referring ${program.name} to your friends and followers.`,
image: `${APP_DOMAIN}/api/og/program?slug=${program.slug}${groupSlug ? `&groupSlug=${groupSlug}` : ""}`,
canonicalUrl: `${PARTNERS_DOMAIN}/${program.slug}`,
});
}Static parameter generation queries the program slug fetcher to pre-render partner application routes for static site generation.
export async function generateStaticParams() {
const programs = await getProgramSlugs();
return programs.map((program) => ({
programSlug: program.slug,
groupSlug: DEFAULT_PARTNER_GROUP.slug,
}));
}Warning
If getProgram returns a null or undefined program object for a given programSlug, the layout immediately triggers Next.js's notFound() helper, returning a 404 response.
Sources: apps/web/app/ee/partners.dub.co/apply/programSlug/default/layout.tsx:58-62
The partner onboarding layout establishes a responsive container featuring an absolute SVG background grid, aurora gradient effects, wordmark branding, and a signed-in user hint component.
export default function PartnerOnboardingLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<div className="absolute inset-0 isolate overflow-hidden bg-white">
<div
className={cn(
"absolute inset-y-0 left-1/2 w-[1200px] -translate-x-1/2",
"[mask-composite:intersect] [mask-image:linear-gradient(black,transparent_320px),linear-gradient(90deg,transparent,black_5%,black_95%,transparent)]",
)}
>
<Grid
cellSize={60}
patternOffset={[0.75, 0]}
className="text-neutral-200"
/>
</div>
<AuroraGradient />
</div>
<div className="relative flex min-h-[100dvh] min-h-screen w-full flex-col items-center overflow-hidden md:justify-between">
<div className="w-full px-4 md:grow md:basis-0 md:px-0">
<div className="flex justify-center pt-4">
<Link
href="https://dub.co/home"
target="_blank"
className="block w-fit"
>
<Wordmark className="h-8" />
<div className="text-center text-sm font-semibold text-black/80">
Partners
</div>
</Link>
</div>
</div>
<div className="w-full flex-1 overflow-y-auto md:flex-none md:overflow-visible">
<div className="w-full px-5 pb-8 pt-8 sm:pb-4 md:px-0 md:py-16">
{children}
</div>
</div>
<div className="w-full md:hidden">
<SignedInHint />
</div>
<div className="hidden md:block md:grow md:basis-0" />
</div>
<div className="hidden md:block">
<SignedInHint />
</div>
<Toolbar show={["help"]} />
</>
);
}Upon completing onboarding, partners receive a structured welcome email generated via React Email. The sequence outlines four explicit onboarding steps linking to target URLs within the Dub ecosystem.
Partner links are generated programmatically or via UI workflows by mapping partner attributes to short link keys and processing them through link creation APIs. The core function derivePartnerLinkKey() evaluates input parameters in a specific fallback sequence: if an explicit key is provided, it is returned immediately; otherwise, the function checks for a username, falls back to slugified partner name, and finally slugifies the local part of the partner's email.
Sources: apps/web/lib/api/partners/generate-partner-link.ts:16-40
When building default partner keys in multi-link scenarios, buildPartnerDefaultLinkKey() wraps the derived slug with optional prefix formatting and appends a randomized 4-character nanoid suffix if multiple default links exist for the partner group.
Sources: apps/web/lib/api/partners/generate-partner-link.ts:46-74
During batch or individual link generation in generatePartnerLink(), key collision handling executes a retry loop: if processLink() returns a conflict error starting with "Duplicate key", a randomized suffix is appended to currentKey, and link processing retries until successful.
Sources: apps/web/lib/api/partners/generate-partner-link.ts:91-164
When a partner creates or submits a custom destination URL, the system validates the URL against group settings and additional link configurations. In PartnerLinkModalContent, destination domains are evaluated against any additionalLinks defined in the partner group. If additional links exist, their domains form the allowed destination list; otherwise, the primary program URL's domain is used.
Sources: apps/web/ui/modals/partner-link-modal.tsx:186-200
Partner endpoints like POST /api/partners/links, POST /api/embed/referrals/links, and POST /api/partner-profile/programs/[programId]/links enforce enrollment status checks and max link limits (group.maxPartnerLinks) before invoking validatePartnerLinkUrl().
Sources: apps/web/app/ee/api/partners/links/route.ts:94-124, apps/web/app/ee/api/embed/referrals/links/route.ts:25-56, apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts:79-133
Once a link passes core processing, applyGroupUtmToLink() enriches the link payload with standardized UTM parameters defined by the partner group's UTM template and the partner's name.
Sources: apps/web/app/ee/api/partners/links/route.ts:189-194, apps/web/app/ee/api/embed/referrals/links/route.ts:135-139, apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts:175-179
For mobile attribution, generatePartnerLink() checks whether the processed destination URL is an AppsFlyer tracking URL using isAppsFlyerTrackingUrl(). When matching parameters are supplied, applyAppsFlyerParameters() injects attribution tokens into the URL query string, passing context objects containing partnerName and partnerLinkKey.
Sources: apps/web/lib/api/partners/generate-partner-link.ts:147-161
Sources: apps/web/app/ee/api/partners/links/route.ts:34-223, apps/web/app/ee/api/embed/referrals/links/route.ts:19-159, apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts:19-186
Warning
When creating partner-level links via POST /api/partners/links, if any link-level reward IDs (clickRewardId, leadRewardId, saleRewardId, discountId) are specified, the workspace plan capability canUseAdvancedRewardLogic must evaluate to true, or the request will fail with a forbidden error.
Sources: apps/web/app/ee/api/partners/links/route.ts:197-214
Embedded referral widgets allow partners to manage their referral links, resources, and payout settings directly inside third-party applications via token-authenticated embeds. The client entry point (ReferralsPageClient) fetches an immutable user referrals token from /api/user/referrals-token and renders <DubEmbed data="referrals" token={publicToken} />.
Sources: apps/web/app/app.dub.co/dashboard/account/settings/referrals/page-client.tsx:11-48
The token issuance pipeline validates session state, inspects workspace eligibility, and automatically provisions or links partner profiles.
When free workspaces request tokens via /api/user/referrals-token, the router checks whether the user has earned commissions, is banned, or joined within the last 30 days. Eligible tenants receive a public token generated by dub.embedTokens.referrals().
Sources: apps/web/app/api/user/referrals-token/route.ts:40-72
The ReferralsEmbedQuickstart component presents an interactive carousel or grid containing quickstart actions, customized by program configuration data parsed via programEmbedSchema.
Sources: apps/web/app/ee/app.dub.co/embed/referrals/quickstart.tsx:22-33
Warning
The receive earnings CTA displays a disabled tooltip preventing withdrawals when earnings.upcoming === 0 && earnings.paid === 0, stating that users can withdraw funds once they complete at least one sale.
Sources: apps/web/app/ee/app.dub.co/embed/referrals/quickstart.tsx:35-136
Partner payout and banking setup relies on server actions that authenticate partner requests, verify role-based permissions (payout_settings.update), check country-specific eligibility using getPayoutMethodsForCountry, and provision or link appropriate Stripe accounts (Stripe Connect or Stripe Recipient accounts) before generating valid redirection links for onboarding or updates.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:1-83, apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:1-76
The Stripe Connect onboarding and link generation flow proceeds through explicit validation and provisioning steps before evaluating account submission status:
authPartnerActionClient.action() — Intercepts the request and injects the authenticated partner and partnerUser context.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:12-14throwIfNoPermission() — Validates that partnerUser.role holds the payout_settings.update permission.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:16-19getPayoutMethodsForCountry() — Verifies that partner.country supports PartnerPayoutMethod.connect, throwing an error displaying the localized country name from COUNTRIES if unsupported.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:35-43createConnectedAccount() — Provisions a new connected account via Stripe if partner.stripeConnectId is missing, persisting the resulting ID via prisma.partner.update().
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:21-57stripe.accounts.retrieve() — Fetches the live account record using partner.stripeConnectId.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:66account.details_submitted is true, calls stripe.accounts.createLoginLink(); otherwise, calls stripe.accountLinks.create() with type: "account_onboarding" and collect: "eventually_due".
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:68-77Warning
Both Stripe actions strictly require partners to have both a valid email and a configured country set in partners.dub.co/settings; omitting either throws an immediate descriptive error preventing account generation.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:23-34, apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:23-34
The recipient account generation flow handles alternative payout methods such as stablecoins by checking country support against PartnerPayoutMethod.stablecoin.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:18-76, apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:18-70
Tip
When generating standard Stripe Connect links, the action dynamically inspects account.details_submitted; if true, it provisions a direct login link via stripe.accounts.createLoginLink(), avoiding redundant onboarding wizard steps for fully verified accounts.
Sources: apps/web/lib/actions/partners/generate-stripe-account-link.ts:68-70
The partner portal analytics and telemetry layer integrates timeseries performance tracking, discount code displays, network referral monitoring, and webhook-driven customer support telemetry via Plain. Partners visualize their earnings, clicks, leads, and sales via synchronized timeseries performance charts powered by SWR hooks like usePartnerEarningsTimeseries and usePartnerAnalytics. Referral performance is accompanied by granular reward configurations and discount code displays.
Sources: apps/web/app/ee/partners.dub.co/dashboard/referrals/page.tsx:242-257, apps/web/app/ee/partners.dub.co/dashboard/programs/programSlug/enrolled/page-client.tsx:12-213, apps/web/app/api/callback/plain/partner/route.ts:1-122
Customer support integrations rely on Plain webhook callbacks authenticated via the X-Plain-Webhook-Secret header. When a support ticket or customer view requests partner telemetry, the webhook route executes a precise verification and database lookup chain.
Sources: apps/web/app/api/callback/plain/partner/route.ts:21-88
Warning
If a Plain customer payload lacks an externalId, the webhook fallback queries the database by email address to automatically resolve and bind the user ID, failing with an empty container response if the email does not exist in the database.
Sources: apps/web/app/api/callback/plain/partner/route.ts:31-47
Partners enrolled in the Dub Network can monitor network referrals, track referred partner counts, and view cumulative commission earnings through dedicated dashboard statistics components. Referral links are constructed using constructPartnerReferralLink and support custom query parameters such as ?via=username.
Sources: apps/web/app/ee/partners.dub.co/dashboard/referrals/page.tsx:242-372, apps/web/lib/partner-referrals/utils.ts:8-22
Tip
Network referral links can target any page on the partners domain by appending ?via={username} directly to the destination URL, allowing partners to deep-link custom landing pages while preserving referral attribution.
Sources: apps/web/app/ee/partners.dub.co/dashboard/referrals/page.tsx:350-369