---
title: "Partner Portal and Onboarding"
description: "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..."
last_updated: "2026-10-05T05:07:35.16912+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/partner-portal-and-onboarding"
---

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

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

- [apps/web/app/ee/partners.dub.co/dashboard/referrals/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/referrals/page.tsx)
- [apps/web/app/ee/api/partners/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partners/links/route.ts)
- [apps/web/lib/middleware/partners.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts)
- [apps/web/app/api/callback/plain/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/callback/plain/partner/route.ts)
- [apps/web/lib/partner-referrals/utils.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/partner-referrals/utils.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/partners/partnerId/links/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/partners/%5BpartnerId%5D/links/page.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/quickstart.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/quickstart.tsx)
- [apps/web/app/ee/api/embed/referrals/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/links/route.ts)
- [apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/links/route.ts)
- [apps/web/app/api/user/referrals-token/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/user/referrals-token/route.ts)
- [apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts)
- [apps/web/app/api/tokens/embed/referrals/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/tokens/embed/referrals/route.ts)
- [apps/web/app/ee/partners.dub.co/auth-login-register/generic/layout.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(auth-login-register)/(generic)/layout.tsx)
- [apps/web/lib/actions/partners/generate-stripe-account-link.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts)
- [apps/web/ui/layout/sidebar/dub-partners-popup.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/layout/sidebar/dub-partners-popup.tsx)
- [apps/web/app/ee/partners.dub.co/dashboard/auth.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/auth.tsx)
- [apps/web/app/ee/partners.dub.co/dashboard/programs/programSlug/enrolled/page-client.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/%5BprogramSlug%5D/(enrolled)/page-client.tsx)
- [apps/web/ui/modals/partner-link-modal.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/partner-link-modal.tsx)
- [apps/web/app/ee/partners.dub.co/onboarding/layout.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(onboarding)/layout.tsx)
- [apps/web/app/ee/partners.dub.co/auth-login-register/partner-banner.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(auth-login-register)/partner-banner.tsx)
- [apps/web/app/ee/partners.dub.co/auth-login-register/side-panel.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(auth-login-register)/side-panel.tsx)
- [apps/web/app/ee/partners.dub.co/auth-other/invite/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(auth-other)/invite/page.tsx)
- [apps/web/app/ee/admin.dub.co/dashboard/partners/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/page.tsx)
- [apps/web/app/app.dub.co/dashboard/account/settings/referrals/page-client.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/account/settings/referrals/page-client.tsx)
- [apps/web/lib/api/partners/generate-partner-link.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/generate-partner-link.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/partners/partnerId/links/referral-links.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/partners/%5BpartnerId%5D/links/referral-links.tsx)
- [apps/web/app/ee/partners.dub.co/dashboard/programs/programSlug/enrolled/links/page-client.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/%5BprogramSlug%5D/(enrolled)/links/page-client.tsx)
- [apps/web/app/ee/partners.dub.co/apply/programSlug/default/layout.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(apply)/%5BprogramSlug%5D/(default)/layout.tsx)
- [packages/email/src/templates/welcome-email-partner.tsx](https://github.com/blade47/dub/blob/HEAD/packages/email/src/templates/welcome-email-partner.tsx)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/program-empty-state.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/program-empty-state.tsx)
</details>

## Overview

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.

Sources: [apps/web/lib/middleware/partners.ts:1-123](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts#L1-L123), [apps/web/lib/partner-referrals/utils.ts:1-22](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/partner-referrals/utils.ts#L1-L22)

## Partner Routing and Authentication Middleware

### Overview

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.

Sources: [apps/web/lib/middleware/partners.ts:1-33](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts#L1-L33)

### Middleware Execution Walkthrough

The `PartnersMiddleware` function executes a multi-step evaluation sequence for every request hitting the partners domain:

1. `parse(req)` — Extracts `path`, `fullPath`, `searchParamsObj`, and `searchParamsString` from the inbound request.
2. `getUserViaToken(req)` — Resolves the user session via authentication tokens.
3. `partnersMarketplaceRedirects(path, searchParamsObj)` — Checks legacy marketplace paths and issues a `301` permanent redirect if a match is found.
4. `partnersProgramRedirects(path)` — Evaluates legacy program redirect rules.
5. Authentication and Enrollment Checks — If no user session exists and `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`.

Sources: [apps/web/lib/middleware/partners.ts:25-103](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts#L25-L103)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts#L91-L102)

### Protected Paths and Authentication Rules

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.

| Path Prefix / Route | Purpose / Description |
|---------------------|----------------------|
| `/programs` | Affiliate programs listing and management |
| `/marketplace` | Partner marketplace discovery |
| `/onboarding` | Partner registration and profile setup |
| `/settings` | Partner account and notification settings |
| `/profile` | User and partner profile management |
| `/messages` | Communications and partner updates |
| `/payouts` | Earnings, banking, and payout configuration |
| `/account` | Account details and security |
| `/invite` | Partner profile invitation acceptance |
| `/rewind` | Historical earnings and year-in-review summaries |

Sources: [apps/web/lib/middleware/partners.ts:12-23](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts#L12-L23)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/partners.ts#L75-L89)

### Client-Side Session and Profile Authorization

Client-side rendering routes use specialized wrapper components such as `PartnerProfileAuth` and `AcceptPartnerInvitePage` to verify SWR session states and handle profile error codes.

```tsx
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;
}
```

Sources: [apps/web/app/ee/partners.dub.co/dashboard/auth.tsx:1-46](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/auth.tsx#L1-L46)

The partner profile authentication layer recognizes specific error codes when connecting external payout channels or checking authorization status.

| Error Code Key | Error Message Displayed |
|----------------|-------------------------|
| `unauthorized` | Unauthorized. You must be logged in https://partners.dub.co to continue. |
| `partner_not_found` | Partner profile not found. |
| `invalid_state` | Invalid or expired state. Please try again from the beginning. |
| `paypal_email_not_verified` | PayPal email address is not verified. Please verify your email address in PayPal and try again. |
| `paypal_account_already_in_use` | The PayPal account you're trying to connect is already in use by another partner. Please use a different PayPal account. |

Sources: [apps/web/app/ee/partners.dub.co/dashboard/auth.tsx:10-20](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/auth.tsx#L10-L20)

## Partner Program Application and Onboarding

### Overview

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(apply)/%5BprogramSlug%5D/(default)/layout.tsx#L1-L110), [packages/email/src/templates/welcome-email-partner.tsx:1-123](https://github.com/blade47/dub/blob/HEAD/packages/email/src/templates/welcome-email-partner.tsx#L1-L123)

### Program Landing Pages and Dynamic Routing

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.

```typescript
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}`,
  });
}
```

Sources: [apps/web/app/ee/partners.dub.co/apply/programSlug/default/layout.tsx:12-38](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(apply)/%5BprogramSlug%5D/(default)/layout.tsx#L12-L38)

Static parameter generation queries the program slug fetcher to pre-render partner application routes for static site generation.

```typescript
export async function generateStaticParams() {
  const programs = await getProgramSlugs();

  return programs.map((program) => ({
    programSlug: program.slug,
    groupSlug: DEFAULT_PARTNER_GROUP.slug,
  }));
}
```

Sources: [apps/web/app/ee/partners.dub.co/apply/programSlug/default/layout.tsx:40-47](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(apply)/%5BprogramSlug%5D/(default)/layout.tsx#L40-L47)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(apply)/%5BprogramSlug%5D/(default)/layout.tsx#L58-L62)

### Onboarding Layout and UI Structure

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.

```tsx
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"]} />
    </>
  );
}
```

Sources: [apps/web/app/ee/partners.dub.co/onboarding/layout.tsx:8-70](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(onboarding)/layout.tsx#L8-L70)

### Partner Welcome Email Sequence

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.

| Step Number | Action Title | Target URL | Purpose |
|-------------|--------------|------------|---------|
| 1 | Complete your partner profile | `https://ship.dub.co/partner-profile` | Fill out partner profile and verify social platforms |
| 2 | Apply to our partner network | `https://ship.dub.co/join-network` | Unlock access to the program marketplace |
| 3 | Join a program | `https://ship.dub.co/marketplace` | Apply to specific brand programs and earn commissions |
| 4 | Set up payouts | `https://ship.dub.co/connect-payouts` | Connect a payout method to receive referral earnings |

Sources: [packages/email/src/templates/welcome-email-partner.tsx:51-114](https://github.com/blade47/dub/blob/HEAD/packages/email/src/templates/welcome-email-partner.tsx#L51-L114)

## Partner Link Generation and Validation

### Referral Link Creation and Key Derivation

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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/generate-partner-link.ts#L16-L40)

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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/generate-partner-link.ts#L46-L74)

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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/generate-partner-link.ts#L91-L164)

### Destination Validation and UTM Templates

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](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/modals/partner-link-modal.tsx#L186-L200)

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partners/links/route.ts#L94-L124), [apps/web/app/ee/api/embed/referrals/links/route.ts:25-56](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/links/route.ts#L25-L56), [apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts:79-133](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/links/route.ts#L79-L133)

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partners/links/route.ts#L189-L194), [apps/web/app/ee/api/embed/referrals/links/route.ts:135-139](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/links/route.ts#L135-L139), [apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts:175-179](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/links/route.ts#L175-L179)

### AppsFlyer Mobile Deep-Linking Integration

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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/partners/generate-partner-link.ts#L147-L161)

### API Endpoints for Partner Links

| Route Path | HTTP Method | Handler / Auth Wrapper | Core Action |
|------------|-------------|------------------------|-------------|
| `/api/partners/links` | GET | `withWorkspace` | Retrieves partner links within a workspace program, optionally including reward fields based on search parameters. |
| `/api/partners/links` | POST | `withWorkspace` | Creates a partner link with optional link-level reward overrides and workspace plan capability checks. |
| `/api/embed/referrals/links` | GET | `withReferralsEmbedToken` | Fetches embedded partner links authenticated via referral embed tokens. |
| `/api/embed/referrals/links` | POST | `withReferralsEmbedToken` | Creates a partner link via the embed widget, respecting group link limits and emitting workspace webhooks. |
| `/api/partner-profile/programs/[programId]/links` | GET | `withPartnerProfile` | Returns partner links in a specific program enriched with resolved reward rules and discount codes. |
| `/api/partner-profile/programs/[programId]/links` | POST | `withPartnerProfile` | Generates a new partner link directly from the partner profile portal interface. |

Sources: [apps/web/app/ee/api/partners/links/route.ts:34-223](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partners/links/route.ts#L34-L223), [apps/web/app/ee/api/embed/referrals/links/route.ts:19-159](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/links/route.ts#L19-L159), [apps/web/app/ee/api/partner-profile/programs/programId/links/route.ts:19-186](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/links/route.ts#L19-L186)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partners/links/route.ts#L197-L214)

## Embedded Referrals and Token Distribution

### Overview

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/account/settings/referrals/page-client.tsx#L11-L48)

### Token Generation and Partner Enrollment Flow

The token issuance pipeline validates session state, inspects workspace eligibility, and automatically provisions or links partner profiles.

```mermaid
sequenceDiagram
    participant Client
    participant API as /api/tokens/embed/referrals
    participant DB as Prisma DB
    participant Dub as Dub SDK

    Client->>API: POST request with tenantId & partner metadata
    API->>DB: Query programEnrollment by partnerId or tenantId
    alt Enrollment missing & partnerProps provided
        API->>DB: Find partner by email
        alt Partner missing or not enrolled
            API->>DB: createAndEnrollPartner()
        end
    end
    API->>Dub: referralsEmbedToken.create()
    Dub-->>API: Return token payload
    API-->>Client: Return 201 with ReferralsEmbedTokenSchema
```

Sources: [apps/web/app/api/tokens/embed/referrals/route.ts:16-117](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/tokens/embed/referrals/route.ts#L16-L117)

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/user/referrals-token/route.ts#L40-L72)

### Quickstart Widget Architecture

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/quickstart.tsx#L22-L33)

| Quickstart Item | Target Tab / Action | Condition / Behavior |
|-----------------|---------------------|----------------------|
| **Share your link** | Copies `links[0]` or navigates to `"Links"` tab | Uses `constructPartnerLink()` with the default group and link. |
| **Program resources** | `"Resources"` tab | Disabled if `hasResources` is false. |
| **Browse the FAQ** | `"FAQ"` tab | Rendered when `programEmbedData.hideEarnings` is true. |
| **Receive earnings** | `"Settings"` tab or external URL | Evaluates Tremendous country support and payout history. |

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/quickstart.tsx:37-159](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/quickstart.tsx#L37-L159)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/quickstart.tsx#L35-L136)

## Payout Setup and Stripe Integration

### Overview

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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L1-L83), [apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:1-76](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts#L1-L76)

### Stripe Connect Account Execution Walkthrough

The Stripe Connect onboarding and link generation flow proceeds through explicit validation and provisioning steps before evaluating account submission status:

1. `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-14](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L12-L14)
2. `throwIfNoPermission()` — Validates that `partnerUser.role` holds the `payout_settings.update` permission.
Sources: [apps/web/lib/actions/partners/generate-stripe-account-link.ts:16-19](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L16-L19)
3. `getPayoutMethodsForCountry()` — 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-43](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L35-L43)
4. `createConnectedAccount()` — 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-57](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L21-L57)
5. `stripe.accounts.retrieve()` — Fetches the live account record using `partner.stripeConnectId`.
Sources: [apps/web/lib/actions/partners/generate-stripe-account-link.ts:66](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L66)
6. Branch evaluation: If `account.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-77](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L68-L77)

> [!WARNING]
> 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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L23-L34), [apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:23-34](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts#L23-L34)

### Stripe Recipient Account Actions and Validation

The recipient account generation flow handles alternative payout methods such as stablecoins by checking country support against `PartnerPayoutMethod.stablecoin`.

```mermaid
sequenceDiagram
    participant Client
    participant Action as generateStripeRecipientAccountLink
    participant DB as Prisma DB
    participant Stripe as Stripe API

    Client->>Action: Invoke action
    Action->>Action: throwIfNoPermission("payout_settings.update")
    alt partner.stripeRecipientId is missing
        Action->>Action: Validate email & country presence
        Action->>Action: getPayoutMethodsForCountry()
        Action->>Stripe: createStripeRecipientAccount()
        Stripe-->>Action: Recipient account object
        Action->>DB: prisma.partner.update(stripeRecipientId)
        Action->>Action: useCase = "account_onboarding"
    else partner.stripeRecipientId exists
        Action->>Action: useCase = "account_update"
    end
    Action->>Stripe: createStripeRecipientAccountLink()
    Stripe-->>Action: Account link object
    Action-->>Client: Return { url }
```

Sources: [apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:12-75](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts#L12-L75)

### Payout Integration Parameters Reference

| Integration Parameter / Constant | Target Function / SDK | Value / Structure | Purpose |
|-----------------------------------|-----------------------|-------------------|---------|
| `payout_settings.update` | `throwIfNoPermission` | Role permission string | Guards payout modification endpoints against unauthorized users. |
| `PartnerPayoutMethod.connect` | `getPayoutMethodsForCountry` | Enum value | Validates country eligibility for standard Stripe Connect payouts. |
| `PartnerPayoutMethod.stablecoin` | `getPayoutMethodsForCountry` | Enum value | Validates country eligibility for stablecoin recipient payouts. |
| `account_onboarding` | `createStripeRecipientAccountLink` / `stripe.accountLinks.create` | Action use case / link type string | Triggers onboarding flow for newly created accounts. |
| `account_update` | `createStripeRecipientAccountLink` | Action use case string | Triggers update flow for already provisioned accounts. |
| `eventually_due` | `stripe.accountLinks.create` | `collect` option | Specifies requirement collection behavior during onboarding. |

Sources: [apps/web/lib/actions/partners/generate-stripe-account-link.ts:18-76](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L18-L76), [apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts:18-70](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-recipient-account-link.ts#L18-L70)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/actions/partners/generate-stripe-account-link.ts#L68-L70)

## Earnings Dashboard and Network Referrals

### Overview

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/referrals/page.tsx#L242-L257), [apps/web/app/ee/partners.dub.co/dashboard/programs/programSlug/enrolled/page-client.tsx:12-213](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/%5BprogramSlug%5D/(enrolled)/page-client.tsx#L12-L213), [apps/web/app/api/callback/plain/partner/route.ts:1-122](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/callback/plain/partner/route.ts#L1-L122)

### Plain Customer Webhook Telemetry Execution Flow

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/callback/plain/partner/route.ts#L21-L88)

```mermaid
sequenceDiagram
    participant Plain as Plain Webhook
    participant Route as POST /api/callback/plain/partner
    participant Prisma as Prisma DB
    participant SDK as Plain SDK

    Plain->>Route: POST request with X-Plain-Webhook-Secret
    Route->>Route: Verify token matches process.env.PLAIN_WEBHOOK_SECRET
    Route->>Route: Parse body via plainCallbackSchema
    alt customer.externalId is missing
        Route->>Prisma: prisma.user.findUnique({ email })
        alt user found
            Route->>Prisma: upsertPlainCustomer({ id, name, email })
            Route-->>Route: Set customer.externalId = user.id
        else user not found
            Route-->>Plain: Return empty container "No user found."
        end
    end
    Route->>Prisma: prisma.partner.findFirst({ users.some: userId }, include: programs)
    alt partnerProfile found
        Route->>SDK: plain.addCustomerToCustomerGroups({ customerId, groupKey: "partners.dub.co" })
        Route-->>Plain: Return JSON UI cards (ID, Name, Country, Stripe, Payouts, Top Programs)
    else partnerProfile missing
        Route-->>Plain: Return empty container "No partner profile found."
    end
```

Sources: [apps/web/app/api/callback/plain/partner/route.ts:21-122](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/callback/plain/partner/route.ts#L21-L122)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/callback/plain/partner/route.ts#L31-L47)

### Plain Webhook Telemetry UI Components Reference

| UI Component / Field | SDK Method / Source Variable | Value / Configuration | Purpose |
|----------------------|------------------------------|------------------------|---------|
| `partner` card key | Response JSON card definition | `{ key: "partner", components: [...] }` | Root container grouping partner telemetry elements for Plain support views. |
| `partners.dub.co` | `plain.addCustomerToCustomerGroups` | Customer group identifier string | Automatically assigns verified partners to the designated customer group in Plain. |
| `Dub Admin View` | `uiComponent.linkButton` | `https://admin.dub.co/partners/network?search={id}&partnerId={id}` | Deep-links support agents directly to the internal Dub admin view for the partner. |
| `Payouts Enabled (UTC)` | `uiComponent.badge` | Green badge if `payoutsEnabledAt` is set, Red ("No") otherwise | Displays timestamp-aware payout activation status in UTC format. |
| `Stripe Recipient Account` | `uiComponent.linkButton` | `https://dashboard.stripe.com/global-payouts/recipients/{id}` | Direct link to Stripe global payouts recipient dashboard. |
| `Stripe Express Account` | `uiComponent.linkButton` | `https://dashboard.stripe.com/connect/accounts/{id}` | Direct link to Stripe Connect express account management. |

Sources: [apps/web/app/api/callback/plain/partner/route.ts:112-249](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/callback/plain/partner/route.ts#L112-L249)

### Network Referral Links and Rewards Monitoring

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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/referrals/page.tsx#L242-L372), [apps/web/lib/partner-referrals/utils.ts:8-22](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/partner-referrals/utils.ts#L8-L22)

> [!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](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/referrals/page.tsx#L350-L369)

## Related

- [Partner Program Management](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/partner-program-management)
- [Embeddable Referral Widgets](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/embeddable-referral-widgets)


## Sitemap

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