---
title: "Embeddable Referral Widgets"
description: "Embeddable Referral Widgets provide a portable interface that allows host applications to integrate partner program dashboards directly into their user settings or interface via secure iframe encap..."
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/embeddable-referral-widgets"
---

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

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

- [apps/web/app/ee/app.dub.co/embed/referrals/token.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/token.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/page-client.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/page-client.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/app.dub.co/embed/referrals/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/page.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/dynamic-height-messenger.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/dynamic-height-messenger.tsx)
- [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/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/app/ee/api/embed/referrals/token/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/token/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)
- [packages/embeds/react/src/embed.tsx](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/links.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/links.tsx)
- [apps/web/app/app.dub.co/embed/support-chat/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/embed/support-chat/page.tsx)
- [packages/embeds/core/src/core.ts](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts)
- [apps/web/app/app.dub.co/dashboard/account/settings/referrals/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/account/settings/referrals/page.tsx)
- [apps/web/ui/partners/groups/design/previews/embed-preview.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/groups/design/previews/embed-preview.tsx)
- [apps/web/lib/embed/referrals/token-class.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/token-class.ts)
- [apps/web/app/ee/app.dub.co/embed/referrals/get-referrals-embed-data.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.ts)
- [apps/web/app/ee/app.dub.co/embed/referrals/faq.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/activity.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/index.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/index.tsx)
- [apps/web/lib/middleware/embed.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/embed.ts)
- [apps/web/ui/layout/sidebar/refer-button.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/layout/sidebar/refer-button.tsx)
- [apps/web/app/ee/partners.dub.co/dashboard/programs/programSlug/enrolled/referrals/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/%5BprogramSlug%5D/(enrolled)/referrals/page.tsx)
- [apps/web/lib/embed/referrals/auth.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/auth.ts)
- [apps/web/app/app.dub.co/embed/support-chat/dynamic-height-messenger.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/embed/support-chat/dynamic-height-messenger.tsx)
- [apps/web/lib/openapi/embed-tokens/index.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/openapi/embed-tokens/index.ts)
- [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/settings.tsx)
- [apps/web/ui/support/embedded-chat.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/support/embedded-chat.tsx)
- [packages/embeds/core/src/types.ts](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/types.ts)
- [packages/embeds/react/src/index.ts](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/index.ts)
</details>

## Overview

Embeddable Referral Widgets provide a portable interface that allows host applications to integrate partner program dashboards directly into their user settings or interface via secure iframe encapsulation. The system addresses the challenge of securely exposing affiliate marketing tools, link tracking stats, and reward management outside the primary platform without sacrificing authentication or data integrity. Key architectural decisions include server-side token generation backed by Upstash Redis persistence, client SDK DOM injection with automated iframe sizing via postMessage synchronization, and streamlined React wrapper integration. Adjacent components such as middleware routing, enrollment verification, and centralized API endpoints coordinate to hydrate partner context and deliver seamless navigation across reward management and payout configurations.

Sources: [packages/embeds/core/src/core.ts:5-103](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L5-L103), [packages/embeds/react/src/embed.tsx:1-40](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx#L1-L40), [apps/web/app/api/tokens/embed/referrals/route.ts:16-122](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/tokens/embed/referrals/route.ts#L16-L122), [apps/web/lib/embed/referrals/token-class.ts:13-33](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/token-class.ts#L13-L33)

## Client SDK Architecture and Rendering

### Overview

The client SDK architecture coordinates core DOM manipulation logic and React wrapper integration to mount referral widgets securely inside host applications. The initialization sequence bridges vanilla TypeScript modules with React component lifecycles using reference hooks and dynamic element creation.

Sources: [packages/embeds/core/src/core.ts:12-142](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L12-L142), [packages/embeds/react/src/embed.tsx:14-40](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx#L14-L40)

### Call-Chain Execution Walkthrough

When a host application mounts an embed, initialization executes a strict call sequence: `DubEmbed` component renders → `DubEmbedInner` fires `useEffect` → `init()` instantiates `DubEmbed` → `renderEmbed()` creates or reuses the DOM container → `createIframe()` builds the iframe element with URL parameters.

```mermaid
sequenceDiagram
    participant React as DubEmbed / Inner
    participant Core as init() / DubEmbed
    participant DOM as document / window

    React->>Core: init({ root, token, data, ... })
    Core->>Core: new DubEmbed(options)
    Core->>Core: renderEmbed()
    Core->>DOM: getElementById(DUB_CONTAINER_ID)
    Core->>DOM: createElement("div") (if missing)
    Core->>Core: createIframe(iframeUrl, token, options)
    Core->>DOM: iframe.appendChild / window.addEventListener("message")
    Core->>DOM: root.appendChild(container)
    Core->>React: returns { destroy }
    Note over React,Core: On unmount / option change
    React->>Core: destroy()
    Core->>DOM: getElementById(DUB_CONTAINER_ID)?.remove()
```

Sources: [packages/embeds/core/src/core.ts:16-102](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L16-L102), [packages/embeds/react/src/embed.tsx:14-40](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx#L14-L40)

> [!WARNING]
> If `token` is omitted during initialization, `renderEmbed` logs an error to the console and immediately returns `null`, preventing the container and iframe from mounting.
> Sources: [packages/embeds/core/src/core.ts:36-39](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L36-L39)

### Configuration Options and Message Types

The core SDK accepts explicit configuration options and listens for specific incoming message events from the hosted iframe via `window.addEventListener`.

| Option / Event | Type / Value | Purpose |
| :--- | :--- | :--- |
| `token` | `string` | Required link authentication token for embed data access. |
| `root` | `HTMLElement` | Target DOM element where the embed container is appended (`document.body` by default). |
| `containerStyles` | `Partial<CSSStyleDeclaration>` | Custom CSS overrides for the root embed container. |
| `data` | `"referrals" \| "analytics"` | Specifies the target data type for the widget (`"referrals"` resolves to `/embed/referrals`). |
| `theme` | `"light" \| "dark" \| "system"` | Theme preference passed as a URL search parameter to the iframe. |
| `themeOptions` | `{ backgroundColor?: string }` | Additional theme customization options serialized into JSON. |
| `onError` | `(error: Error) => void` | Callback triggered when an `ERROR` message is received from the iframe. |
| `ERROR` event | `IframeMessage` | Handles error codes and messages dispatched from the frame. |
| `PAGE_HEIGHT` event | `IframeMessage` | Dynamically updates the embed container height upon receiving height updates. |

Sources: [packages/embeds/core/src/core.ts:30-89](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L30-L89), [packages/embeds/core/src/types.ts:9-51](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/types.ts#L9-L51)

### React Wrapper Integration

The React integration layer wraps the core imperative SDK inside declarative components. The `DubEmbed` component uses `memo` and delegates to `DubEmbedInner`, which assigns a unique identifier via `useId()`, sets up a `useRef<HTMLDivElement>`, and runs an `useEffect` hook that triggers `init()` and cleanup via `destroy()`.

```tsx
export const DubEmbed = memo(
  ({ token, data, options, ...rest }: DubEmbedProps) => (
    <DubEmbedInner options={{ ...options, token, data }} {...rest} />
  ),
);
```

Sources: [packages/embeds/react/src/embed.tsx:14-40](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx#L14-L40)

> [!NOTE]
> The `useEffect` dependency array serializes options using `JSON.stringify(options)` alongside the React `useId()` value to safely re-initialize the embed when configuration properties change without incurring reference equality bugs.
> Sources: [packages/embeds/react/src/embed.tsx:37-37](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx#L37-L37)

### Design Trade-Offs

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Imperative core SDK wrapped in React `useEffect` | Allows the core widget logic to be consumed in vanilla JavaScript or any framework while offering a clean React component facade. | Requires manual serialization of options in React hooks (`JSON.stringify`) to track deep configuration updates accurately. |
| Global singleton container ID check (`DUB_CONTAINER_ID`) | Prevents duplicate container injection if multiple render cycles or strict mode mount instances concurrently. | Restricts a single page to running one active embed instance under the default container identifier. |
| Hostname-based environment resolution (`localhost`, `preview.dub.co`, `app.dub.co`) | Automatically routes iframe requests to the correct local, preview, or production domain without requiring explicit host configuration. | Ties SDK deployment behavior directly to specific hostname strings matching the primary platform domains. |

Sources: [packages/embeds/core/src/core.ts:32-55](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L32-L55), [packages/embeds/react/src/embed.tsx:27-38](https://github.com/blade47/dub/blob/HEAD/packages/embeds/react/src/embed.tsx#L27-L38)

## Token Generation and Authentication Flow

### Overview

The token generation and authentication subsystem governs how referral embed tokens are created, persisted in Upstash Redis, validated by server-side middleware, and used to authorize requests against program enrollments. The lifecycle begins when an authenticated client requests a token through either the workspace-scoped API (`POST /api/tokens/embed/referrals`), user-level onboarding routes (`GET /api/user/referrals-token`), or specialized authentication wrappers (`withReferralsEmbedToken`).

Sources: [apps/web/app/api/tokens/embed/referrals/route.ts:16-122](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/tokens/embed/referrals/route.ts#L16-L122), [apps/web/app/api/user/referrals-token/route.ts:11-74](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/user/referrals-token/route.ts#L11-L74), [apps/web/lib/embed/referrals/auth.ts:33-134](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/auth.ts#L33-L134)

### Token Generation and Persistence

The `ReferralsEmbedToken` class handles the creation and retrieval of public tokens. Tokens are generated using a unique identifier prefixed with `EMBED_PUBLIC_TOKEN_PREFIX` and persisted inside Upstash Redis with a strict time-to-live (`EMBED_PUBLIC_TOKEN_EXPIRY`).

```typescript
class ReferralsEmbedToken {
  async create(props: ReferralsEmbedTokenProps) {
    const publicToken = createId({
      prefix: EMBED_PUBLIC_TOKEN_PREFIX,
    });

    await redis.set(publicToken, JSON.stringify(props), {
      ex: EMBED_PUBLIC_TOKEN_EXPIRY,
      nx: true,
    });

    return {
      publicToken,
      expires: new Date(Date.now() + EMBED_PUBLIC_TOKEN_EXPIRY * 1000),
    };
  }

  async get(token: string) {
    return await redis.get<ReferralsEmbedTokenProps>(token);
  }
}
```

Sources: [apps/web/lib/embed/referrals/token-class.ts:13-33](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/token-class.ts#L13-L33)

> [!NOTE]
> The `nx: true` option in `redis.set` ensures that token creation fails if a key collision occurs, guaranteeing uniqueness across generated embed tokens.
> Sources: [apps/web/lib/embed/referrals/token-class.ts:19-22](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/token-class.ts#L19-L22)

### Authentication and Authorization Flow

The `withReferralsEmbedToken` wrapper secures API routes by extracting the bearer token from the `Authorization` header, validating it against Redis, enforcing rate limits, and fetching the associated program enrollment from the database.

```mermaid
sequenceDiagram
    participant Client
    participant Route as withReferralsEmbedToken
    participant Redis as Upstash Redis
    participant DB as Prisma PostgreSQL

    Client->>Route: GET /api/embed/referrals/token (Authorization: Bearer <token>)
    Route->>Redis: referralsEmbedToken.get(embedToken)
    Redis-->>Route: { programId, partnerId }
    Route->>Redis: ratelimit(60, "1 m").limit(embedToken)
    Redis-->>Route: { success, limit, remaining, reset }
    Route->>DB: prisma.programEnrollment.findUniqueOrThrow(...)
    DB-->>Route: { program, links, partnerGroup, ...programEnrollment }
    Route->>Client: NextResponse.json(embedToken)
```

Sources: [apps/web/lib/embed/referrals/auth.ts:42-128](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/auth.ts#L42-L128)

### Middleware Route Authentication

Embed routes and token parameters are governed by `EmbedMiddleware`. Incoming requests are inspected for query parameters or support paths, and rewritten to internal application endpoints or redirected accordingly.

```typescript
export function EmbedMiddleware(req: NextRequest) {
  const { path, searchParamsObj, fullPath } = parse(req);

  if (path.startsWith("/embed/support-chat")) {
    return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url));
  }

  if (searchParamsObj.token) {
    return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url));
  }

  return NextResponse.redirect(new URL("/", req.url));
}
```

Sources: [apps/web/lib/middleware/embed.ts:4-17](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/middleware/embed.ts#L4-L17)

### Design Trade-Offs

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Storing token payloads directly in Upstash Redis | Provides sub-millisecond token resolution without requiring heavy relational database lookups for every session check. | Requires careful TTL management to ensure expired tokens are purged automatically. |
| Bearer token extraction via `Authorization` header | Aligns with standard HTTP REST authentication conventions for secure token transmission. | Relies on client-side headers being correctly attached by the iframe hosting wrapper. |
| Rate-limiting per token identifier (`ratelimit` via Redis) | Protects downstream program enrollment queries from abuse and brute-force inspection. | Adds latency checking overhead to every authenticated request. |

Sources: [apps/web/lib/embed/referrals/token-class.ts:19-31](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/token-class.ts#L19-L31), [apps/web/lib/embed/referrals/auth.ts:48-82](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/embed/referrals/auth.ts#L48-L82)

## Dynamic Resizing and Cross-Window Messaging

### Overview

The dynamic resizing and cross-window messaging subsystem synchronizes iframe dimensions between the embedded widget application and the host page. It relies on a bidirectional `window.postMessage` protocol combined with a `ResizeObserver` running inside the iframe document. When content changes inside the widget, height updates are dispatched to the parent window, which resizes the host container element accordingly.

Sources: [packages/embeds/core/src/core.ts:68-90](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L68-L90), [apps/web/app/ee/app.dub.co/embed/referrals/dynamic-height-messenger.tsx:5-27](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/dynamic-height-messenger.tsx#L5-L27)

### Iframe URL Generation and Parameters

When the `DubEmbed` core initializes an embed, it constructs an iframe URL appending parameters such as `token`, `theme`, `themeOptions`, and sets `dynamicHeight: "true"` to signal support for dynamic resizing scripts.

```typescript
const createIframe = (
  iframeUrl: string,
  token: string,
  options: Pick<DubEmbedOptions, "theme" | "themeOptions">,
): HTMLIFrameElement => {
  const iframe = document.createElement("iframe");

  const params = new URLSearchParams({
    token,
    ...(options.theme ? { theme: options.theme } : {}),
    ...(options.themeOptions
      ? { themeOptions: JSON.stringify(options.themeOptions) }
      : {}),

    // Allows the iframe content to set overflow values and send height messages without affecting older embed scripts
    dynamicHeight: "true",
  });

  iframe.src = `${iframeUrl}?${params.toString()}`;
  iframe.style.width = "100%";
  iframe.style.height = "100%";
  iframe.style.border = "none";
  iframe.setAttribute("credentialssupport", "");
  iframe.setAttribute("allow", "clipboard-write");

  return iframe;
};
```

Sources: [packages/embeds/core/src/core.ts:105-131](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L105-L131)

### Height Observation and Message Dispatch

Inside the referral widget and support chat applications, `DynamicHeightMessenger` and `SupportChatDynamicHeightMessenger` lock the document body overflow to hidden and instantiate a `ResizeObserver` monitoring `document.body`. Every time the body dimensions shift, `update()` calculates `document.body.scrollHeight` and transmits a `PAGE_HEIGHT` message via `parent.postMessage`.

```typescript
export function DynamicHeightMessenger() {
  useEffect(() => {
    document.body.style.overflow = "hidden";
    const update = () => {
      const height = document.body.scrollHeight;
      parent.postMessage(
        {
          originator: "Dub",
          event: "PAGE_HEIGHT",
          data: { height },
        },
        "*",
      );
    };
    update();

    const resizeObserver = new ResizeObserver(update);
    resizeObserver.observe(document.body);

    return () => {
      resizeObserver.disconnect();
    };
  }, []);

  return false;
}
```

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/dynamic-height-messenger.tsx:5-30](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/dynamic-height-messenger.tsx#L5-L30)

> [!NOTE]
> Both referral embeddings and support chat implementations use identical messenger mechanics, ensuring uniform behavior across distinct embedded components.
> Sources: [apps/web/app/app.dub.co/embed/support-chat/dynamic-height-messenger.tsx:5-28](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/embed/support-chat/dynamic-height-messenger.tsx#L5-L28)

### Host Event Listener and Container Resizing

The host page registers a `message` event listener on `window` inside `DubEmbed.renderEmbed()`. When an incoming message with `event: "PAGE_HEIGHT"` arrives, it targets the container element by `DUB_CONTAINER_ID` and updates its CSS height property to match the reported scroll height.

```typescript
    // Listen the message from the iframe
    window.addEventListener("message", (e) => {
      const { data, event } = e.data as IframeMessage;

      console.debug("[Dub] Iframe message", data);

      switch (event) {
        case "ERROR":
          onError?.(
            new EmbedError({
              code: data?.code ?? "",
              message: data?.message ?? "",
            }),
          );
          break;
        case "PAGE_HEIGHT": {
          const container = document.getElementById(DUB_CONTAINER_ID);
          if (container) container.style.height = `${data.height}px`;

          break;
        }
      }
    });
```

Sources: [packages/embeds/core/src/core.ts:68-90](https://github.com/blade47/dub/blob/HEAD/packages/embeds/core/src/core.ts#L68-L90)

## Widget Page Layout and Data Hydration

### Overview

The referral embed server-side entry point processes incoming requests inside Next.js Server Components, extracts query search parameters including `token`, `themeOptions`, and `dynamicHeight`, and hydrates the page client context. When the `ReferralsEmbedPage` component loads, it wraps execution in a `Suspense` boundary utilizing `EmbedInlineLoading` as a fallback, while `ReferralsEmbedRSC` coordinates data fetching via `getReferralsEmbedData(token)` before mounting `ReferralsEmbedPageClient`.

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/page.tsx:9-58](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/page.tsx#L9-L58)

### Enrollment Verification and Data Hydration

The `getReferralsEmbedData` asynchronous function validates the incoming token using `referralsEmbedToken.get(token)`. If either `programId` or `partnerId` is missing, it triggers a `notFound()` response. It subsequently calls `getProgramEnrollmentOrThrow` to load the partner enrollment record with nested relations including partner platforms, program metadata, links with associated link rewards, click rewards, lead rewards, sale rewards, referral rewards, custom rewards, discounts, and partner groups.

```typescript
export const getReferralsEmbedData = async (token: string) => {
  const { programId, partnerId } = (await referralsEmbedToken.get(token)) ?? {};

  if (!programId || !partnerId) {
    notFound();
  }

  const programEnrollment = await getProgramEnrollmentOrThrow({
    partnerId,
    programId,
    include: {
      partner: {
        select: {
          id: true,
          name: true,
          email: true,
          username: true,
          country: true,
          tremendousEmail: true,
          defaultPayoutMethod: true,
          platforms: {
            select: {
              type: true,
              identifier: true,
              verifiedAt: true,
            },
          },
        },
      },
      program: {
        select: {
          id: true,
          name: true,
          slug: true,
          domain: true,
          defaultGroupId: true,
          minPayoutAmount: true,
          termsUrl: true,
          embedData: true,
          resources: true,
        },
      },
      links: {
        include: {
          linkReward: {
            include: {
              clickReward: true,
              leadReward: true,
              saleReward: true,
              discount: true,
            },
          },
        },
      },
      partnerGroup: true,
      clickReward: true,
      leadReward: true,
      saleReward: true,
      referralReward: true,
      customReward: true,
      discount: true,
      programPartnerTags: {
        select: {
          partnerTagId: true,
        },
      },
    },
  });

  if (!programEnrollment || !programEnrollment.partnerGroup) {
    notFound();
  }
...
```

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/get-referrals-embed-data.ts:14-85](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.ts#L14-L85)

> [!WARNING]
> If a partner attempts to load an embed widget for a program where their enrollment is missing or inactive, `getProgramEnrollmentOrThrow` and subsequent null checks will throw or trigger a 404 error, preventing unauthorized rendering.
> Sources: [apps/web/app/ee/app.dub.co/embed/referrals/get-referrals-embed-data.ts:17-19](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.ts#L17-L19), [apps/web/app/ee/app.dub.co/embed/referrals/get-referrals-embed-data.ts:83-85](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.ts#L83-L85)

### Commissions Aggregation and Payout Metrics

Concurrently with partner bounty checks via `getBountiesForPartner`, `getReferralsEmbedData` queries the database for `Commission` records grouped by status, filtering for records with `earnings: { gt: 0 }` for the specific `programId` and `partnerId`. It aggregates partner link stats using `aggregatePartnerLinksStats(links)`.

```typescript
  const { totalClicks, totalLeads, totalConversions } =
    aggregatePartnerLinksStats(links);

  const [commissions, bounties] = await Promise.all([
    prisma.commission.groupBy({
      by: ["status"],
      _sum: {
        earnings: true,
      },
      _count: {
        id: true,
      },
      where: {
        earnings: {
          gt: 0,
        },
        programId,
        partnerId,
      },
    }),

    getBountiesForPartner(programEnrollment),
  ]);
```

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/get-referrals-embed-data.ts:100-122](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.ts#L100-L122)

### Client Hydration and Token Monitoring

Once data is returned from the server component, `ReferralsEmbedPageClient` parses resource and embed schemas, evaluates whether the partner has active embed access via `ACTIVE_ENROLLMENT_STATUSES`, and renders either an unapproved fallback view or the full dashboard wrapped inside `ReferralsEmbedDataProvider`. 

```typescript
export const ReferralsReferralsEmbedToken = () => {
  const token = useEmbedToken();

  const { error } = useSWR<{ token: number }>(
    "/api/embed/referrals/token",
    (url) =>
      fetcher(url, {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      }),
    {
      revalidateOnFocus: true,
      dedupingInterval: 30000,
      keepPreviousData: true,
    },
  );

  // Inform the parent if there's an error (Eg: token is expired)
  useEffect(() => {
    if (error) {
      window.parent.postMessage(
        {
          originator: "Dub",
          event: "ERROR",
          data: error.info,
        },
        "*",
      );
    }
  }, [error]);

  return null;
};
```

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/token.tsx:8-41](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/token.tsx#L8-L41)

## Referral Management and Payout UI

### Overview

Referral management and payout UI within embedded widgets enables partners to generate affiliate links, view activity metrics, explore bounties, select payout methods, and view program FAQs. The host dashboard integrates these views by mounting client entrypoints like `ReferralsPageClient`, which verifies public embed tokens via SWR or renders empty states when tokens are missing.
Sources: [apps/web/app/app.dub.co/dashboard/account/settings/referrals/page-client.tsx:11-49](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/account/settings/referrals/page-client.tsx#L11-L49)

### Affiliate Link Generation and Management

The link management UI toggles between the links list view (`ReferralsEmbedLinksList`) and the creation/update form (`ReferralsEmbedCreateUpdateLink`). Partners can manage their custom referral URLs using construct utilities.
Sources: [apps/web/app/ee/app.dub.co/embed/referrals/links.tsx:6-31](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/links.tsx#L6-L31), [apps/web/app/ee/app.dub.co/embed/referrals/quickstart.tsx:1](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/quickstart.tsx#L1-L1)

### Activity Tracking and Analytics

The `ReferralsEmbedActivity` component renders aggregate performance metrics for clicks, leads, and conversions. When statistics are non-zero, it fetches composite analytics via SWR with a 1-year annual interval and composites timeseries data.
Sources: [apps/web/app/ee/app.dub.co/embed/referrals/activity.tsx:10-40](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx#L10-L40)

```typescript
  const analyticsSearchParams = new URLSearchParams({
    event: "composite",
    groupBy: "timeseries",
    interval: "1y",
    saleType: "new",
  });

  const { data: analytics } = useSWR<AnalyticsTimeseries[]>(
    !isEmpty &&
      `/api/embed/referrals/analytics?${analyticsSearchParams.toString()}`,
    (url) =>
      fetcher(url, {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      }),
    {
      keepPreviousData: true,
      dedupingInterval: 60000,
    },
  );
```
Sources: [apps/web/app/ee/app.dub.co/embed/referrals/activity.tsx:20-40](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx#L20-L40)

### Reward Payouts and Payout Methods

Partners configure payout destinations through `ReferralsEmbedSettings`, which supports two primary payout methods: Tremendous gift cards and direct cash registrations.
Sources: [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx:506-546](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/settings.tsx#L506-L546)

| Payout Method | Configuration Identifier | Minimum/Maximum Limits | Authentication Flow |
| :--- | :--- | :--- | :--- |
| **Gift Cards (Tremendous)** | `tremendous` | Min: `$10.00` (1000 cents), Max: `$10,000.00` | Email submission → OTP Request → 6-digit verification code input via `OTPInput` |
| **Cash** | Custom / External | Determined by program terms | External redirect to registration or SSO login endpoint |

Sources: [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx:6-7](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/settings.tsx#L6-L7), [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx:246-277](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/settings.tsx#L246-L277), [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx:509-543](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/settings.tsx#L509-L543)

> [!WARNING]
> Payout methods are mutually exclusive and permanent. Once a partner connects a default payout method (`partner.defaultPayoutMethod`), any other payout option is disabled with a tooltip explaining that multiple methods cannot be combined, and existing methods cannot be changed through the widget interface.
> Sources: [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx:355-360](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/settings.tsx#L355-L360), [apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx:521-523](https://github.com/blade47/dub/blob/HEAD/apps/web/app/ee/app.dub.co/embed/referrals/settings.tsx#L521-L523)

### Bounties and FAQ Embed Integration

The referrals UI also surfaces competitive program incentives via `ReferralsEmbedBounties`, allowing partners to view individual bounties, track completion periods, and inspect program details. Program FAQs render dynamic reward commission calculations using `constructRewardAmount` alongside embedded schemas.
Sources: [apps/web/app/ee/app.dub.co/embed/referrals/bounties/index.tsx:16-102](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/index.tsx#L16-L102), [apps/web/app/ee/app.dub.co/embed/referrals/faq.tsx:14-26](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx#L14-L26)

## Related

- [Partner Portal and Onboarding](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/partner-portal-and-onboarding)
- [OAuth2 Provider and API Tokens](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/authentication-and-security/oauth2-provider-and-api-tokens)


## Sitemap

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