# Introduction
Source: https://dub.co/docs



[Dub](https://dub.co) is the modern link attribution platform for you to [run partner programs](/help/article/dub-partners), [track conversion analytics](/docs/concepts/attribution), and [create short links](/docs/concepts/links/introduction).

Whether you are:

* a founder looking to [launch an affiliate or referral program](/help/article/dub-partners)
* a marketer looking to [track the success of your campaigns](/docs/concepts/attribution)
* a developer looking to [integrate link analytics](/docs/concepts/analytics/introduction) into your application

We've got you covered.

## Key features

Dub offers a suite of tools to help you grow your business with partnerships and measure attribution across all your marketing channels.

Here are some of the features that Dub offers:

* [Partner programs](/help/article/dub-partners) (affiliates, influencers, user referrals)
* [Marketing attribution](/docs/concepts/attribution)
* [Real-time analytics](/docs/concepts/analytics/introduction)
* [Short links](/docs/concepts/links/introduction) (including [bulk link creation](/docs/concepts/links/bulk-operations))
* [Event webhooks](/docs/webhooks/introduction)
* [Integrations](https://dub.co/integrations) + [building custom integrations](/docs/integrations/quickstart)
* [REST API](/docs/api-reference/introduction) + [native SDKs](/docs/sdks/overview) for multiple languages

## What would you like to do?

<CardGroup>
  <Card title="Set up a partner program" icon="users" href="/help/article/setting-up-your-program">
    Scale partnerships with affiliates, influencers, and user referrals
  </Card>

  <Card title="Track conversion events" icon="chart-column" href="/docs/concepts/attribution">
    Measure the success of your marketing campaigns
  </Card>

  <Card title="Create short links" icon="link" href="/docs/concepts/links/introduction">
    Generate short links for your marketing / SMS campaigns
  </Card>

  <Card title="Integrate with webhooks" icon="webhook" href="/docs/webhooks/introduction">
    Build custom workflows with Dub's webhooks
  </Card>
</CardGroup>

## Popular integrations

<CardGroup>
  <Card title="Stripe" icon="stripe-s" href="/docs/integrations/stripe">
    Track recurring revenue, refunds, free trials, churn, and more
  </Card>

  <Card title="Shopify" icon="shopify" href="/docs/integrations/shopify">
    Native integration for Shopify – 1-click install, no code required
  </Card>

  <Card title="Google Tag Manager" icon="google" href="/docs/integrations/google-tag-manager">
    Track conversion events via GTM
  </Card>

  <Card title="Zapier" icon="bolt" href="https://dub.co/integrations/zapier">
    Connect Dub with 7,000+ apps
  </Card>

  <Card title="HubSpot" icon="hubspot" href="/docs/integrations/hubspot">
    Sync leads and referrals to your CRM
  </Card>

  <Card title="Segment" icon="arrow-progress" href="/docs/integrations/segment">
    Track conversion events via Segment
  </Card>
</CardGroup>

[View all integrations ↗](https://dub.co/integrations)

## SDKs

<Tabs>
  <Tab title="Server-side SDKs">
    <CardGroup>
      <Card title="TypeScript SDK" icon="js" href="/docs/sdks/typescript" />

      <Card title="Python SDK" icon="python" href="/docs/sdks/python" />

      <Card title="Go SDK" icon="golang" href="/docs/sdks/go" />

      <Card title="PHP SDK" icon="php" href="/docs/sdks/php" />

      <Card title="Ruby SDK" icon="gem" href="/docs/sdks/ruby" />
    </CardGroup>
  </Tab>

  <Tab title="Client-side SDKs">
    <CardGroup>
      <Card title="Client-side SDK (Web)" icon="js" href="/docs/sdks/client-side/introduction" />

      <Card title="Client-side SDK (iOS)" icon="swift" href="/docs/sdks/client-side-mobile/installation-guides/swift" />

      <Card title="Client-side SDK (React Native)" icon="react" href="/docs/sdks/client-side-mobile/installation-guides/react-native" />
    </CardGroup>
  </Tab>
</Tabs>

## Stay up to date

<CardGroup>
  <Card title="Blog" icon="newspaper" href="https://dub.co/blog">
    Latest features and updates from Dub
  </Card>

  <Card title="X / Twitter" icon="twitter" href="https://x.com/dubdotco">
    Follow us for release announcements
  </Card>
</CardGroup>


# Retrieve analytics
Source: https://dub.co/docs/api-reference/analytics/retrieve

get /analytics
Retrieve analytics for a link, a domain, or the authenticated workspace. The response type depends on the `event` and `type` query parameters.

<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>


# API Authentication
Source: https://dub.co/docs/api-reference/authentication



## Introduction

Dub uses two distinct types of authentication keys depending on where your code runs. Choose the right one for your use case:

| Key type                                  | Use case                        | Where to use                                          |
| :---------------------------------------- | :------------------------------ | :---------------------------------------------------- |
| [**API keys**](#api-keys)                 | Server-to-server (REST APIs)    | Back-end only; store securely, never expose to client |
| [**Publishable keys**](#publishable-keys) | Client-side conversion tracking | Safe to include in frontend code                      |

## API keys

API keys on Dub allow you to access your workspace programmatically. This is useful for integrating Dub into your application or with other tools and services.

Each API key is tied to a specific workspace – meaning you can use it to access that workspace's resources without having to worry about "leaking" access to other workspaces.

API keys on Dub follow the format:

```bash .env theme={null}
DUB_API_KEY=dub_xxxxxxxx
```

By default, you can use this key to perform any API request without restriction, so it must be stored securely in your app's server-side code (such as in an environment variable or credential management system). Don’t expose this key on a website.

### Create an API key

You can create an API key by following these steps:

<Steps>
  <Step title="Go to your workspace">
    Go to **Settings** > [**API Keys**](https://app.dub.co/settings/tokens) in your workspace.

    <Frame>
      <img alt="Workspace API keys on Dub" />
    </Frame>
  </Step>

  <Step title="Create an API Key">
    Click on the "Create" button and select permissions you want to grant to
    the API key.

    Select between "You" and "Machine" to associate the API key with your account or a [machine user](#machine-users).

    * **You**: This API key is tied to your user and can make requests against the selected workspace.
    * **Machine**: A machine user will be added to your workspace, and an API key associated with that machine user will be created.

    <Frame>
      <img alt="Add new API key on Dub" />
    </Frame>

    Click on the **Create API Key** button to create the key. Make sure to copy your API key and store it in a safe place. You won't be able to see it again.

    <Frame>
      <img alt="API Key created on Dub" />
    </Frame>
  </Step>

  <Step title="Use your API Key">
    Now that you have your API key, you can use it to access your workspace's resources programmatically via SDKs or within any API request as a bearer token.

    ```
    Authorization: Bearer dub_xxxx
    ```
  </Step>
</Steps>

<Tip>
  We recommend creating API keys with the least privilege necessary to perform
  the required tasks. This helps to reduce the risk of unauthorized access to
  your workspace.
</Tip>

### API key permissions

When creating a secret key, you can select the permissions it has, which will give the key access to certain (or all) resources on Dub. Here are the different permission options:

| Permission          | Description                                                                                                                                                                                                                                                 |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **All permissions** | This API key will have full access to all resources.                                                                                                                                                                                                        |
| **Read only**       | This API key will have read-only access to all resources.                                                                                                                                                                                                   |
| **Restricted**      | This API key will have restricted access to some resources: <ul><li>[Links](/docs/data-model#links)</li><li>[Analytics](/docs/api-reference/analytics/retrieve)</li><li>[Domains](/docs/data-model#domains)</li><li>[Tags](/docs/data-model#tags)</li></ul> |

Depending on your use case, you might want to use one of these 3 options to limit the scope of the API key and improve security. When making API calls, if your API key has insufficient permissions, the error should tell you which permissions you need.

### Machine users

On Dub, you can create API keys that are associated with a "Machine user". This is particularly helpful when you don't want to associate the API key with a particular user in your workspace, to avoid security risks in involving turnover or changes in project ownership.

<Warning>
  Machine users share the same permissions as the [owner
  role](/help/article/workspace-roles#owner-role) in a workspace. Make sure to
  only create machine users for trusted applications.
</Warning>

<Frame>
  <img alt="Creating an API key associated with a machine user on Dub" />
</Frame>

These machine users will show up on your workspace's **People** tab, but will not contribute to your workspace's user count.

<Frame>
  <img alt="Machine user on Dub" />
</Frame>

<Tip>
  If you delete an API key associated with a machine user, the machine user will
  be deleted. Vice versa, if you delete a machine user, their corresponding API
  key will be deleted as well.
</Tip>

## Publishable keys

Publishable keys on Dub allow you to safely embed authentication in client-side applications.

These keys are specifically designed to be used with [Dub's client-side SDKs](/docs/sdks/client-side/introduction) for features like [conversion tracking](/docs/sdks/client-side/features/conversion-tracking).

Unlike [API keys](#api-keys) which must be kept secret, publishable keys can be safely exposed in your frontend code since they have limited capabilities.

Publishable keys on Dub follow the format:

```bash .env theme={null}
DUB_PUBLISHABLE_KEY=dub_pk_xxxxxxxxxxxxxxxxxxxxxxxx
```

### Create a publishable key

You can create a publishable key by following these steps:

<Steps>
  <Step title="Generate your publishable key">
    Before you can track conversions on the client-side, you need to generate a [publishable key](/docs/api-reference/authentication#publishable-keys) from your Dub workspace.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>
  </Step>

  <Step title="Allowlist your site's domain">
    Then, you'll need to allowlist your site's domain to allow the client-side conversion events to be ingested by Dub.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and add your site's domain to the **Allowed Hostnames** list.

    This provides an additional layer of security by ensuring only authorized domains can track conversions using your publishable key.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    You can group your hostnames when adding them to the allow list:

    * `example.com`: Tracks traffic **only** from `example.com`.
    * `*.example.com`: Tracks traffic from **all subdomains** of `example.com`, but **not** from `example.com` itself.

    <Tip>
      When testing things out locally, you can add `localhost` to the **Allowed
      Hostnames** list temporarily. This will allow local events to be ingested by
      Dub. Don't forget to remove it once you're ready to go live!
    </Tip>
  </Step>

  <Step title="Use your publishable key">
    You can now use your publishable key to authenticate client-side requests in your application. Usage will depend on the client-side SDK you are using.
  </Step>
</Steps>


# Approve a bounty submission
Source: https://dub.co/docs/api-reference/bounties/approve

post /bounties/{bountyId}/submissions/{submissionId}/approve
Approve a bounty submission. Optionally specify a custom reward amount.



# List bounty submissions
Source: https://dub.co/docs/api-reference/bounties/list

get /bounties/{bountyId}/submissions
List all submissions for a specific bounty in your partner program.



# Reject a bounty submission
Source: https://dub.co/docs/api-reference/bounties/reject

post /bounties/{bountyId}/submissions/{submissionId}/reject
Reject a bounty submission with a specified reason and optional note.



# Create commission
Source: https://dub.co/docs/api-reference/commissions/create

post /commissions
Create one or more commissions (custom, lead or sale) for a partner. Commission creation is processed asynchronously. Use the List Commissions endpoint or webhooks to be notified when the commission is created.



# List all commissions
Source: https://dub.co/docs/api-reference/commissions/list

get /commissions
Retrieve a paginated list of commissions for your partner program.



# Update a commission
Source: https://dub.co/docs/api-reference/commissions/update

patch /commissions/{id}
Update an existing commission amount. This is useful for handling refunds (partial or full) or fraudulent sales.



# Delete a customer
Source: https://dub.co/docs/api-reference/customers/delete

delete /customers/{id}
Delete a customer from a workspace.



# List all customers
Source: https://dub.co/docs/api-reference/customers/list

get /customers
Retrieve a paginated list of customers for the authenticated workspace.



# Retrieve a customer
Source: https://dub.co/docs/api-reference/customers/retrieve

get /customers/{id}
Retrieve a customer by ID for the authenticated workspace. To retrieve a customer by external ID, prefix the ID with `ext_`.



# Update a customer
Source: https://dub.co/docs/api-reference/customers/update

patch /customers/{id}
Update a customer for the authenticated workspace.



# Check the availability of one or more domains
Source: https://dub.co/docs/api-reference/domains/check-availability

get /domains/status
Check if a domain name is available for purchase. You can check multiple domains at once.

<Note>
  Domain registration is only available for certain [Enterprise
  customers](https://dub.co/enterprise). Please [contact
  us](https://dub.co/contact/sales) to get access.
</Note>


# Create a domain
Source: https://dub.co/docs/api-reference/domains/create

post /domains
Create a domain for the authenticated workspace.



# Delete a domain
Source: https://dub.co/docs/api-reference/domains/delete

delete /domains/{slug}
Delete a domain from a workspace. It cannot be undone. This will also delete all the links associated with the domain.



# Register a domain
Source: https://dub.co/docs/api-reference/domains/register

post /domains/register
Register a domain for the authenticated workspace. Only available for Enterprise Plans.

<Note>
  Domain registration is only available for certain [Enterprise
  customers](https://dub.co/enterprise). Please [contact
  us](https://dub.co/contact/sales) to get access.
</Note>


# Update a domain
Source: https://dub.co/docs/api-reference/domains/update

patch /domains/{slug}
Update a domain for the authenticated workspace.



# List all events
Source: https://dub.co/docs/api-reference/events/list

get /events
Retrieve a paginated list of events for the authenticated workspace.

<Note>
  Events endpoints require a [Business plan](https://dub.co/pricing/partners)
  subscription or higher.
</Note>


# Create a folder
Source: https://dub.co/docs/api-reference/folders/create

post /folders
Create a folder for the authenticated workspace.



# Delete a folder
Source: https://dub.co/docs/api-reference/folders/delete

delete /folders/{id}
Delete a folder from the workspace. All existing links will still work, but they will no longer be associated with this folder.



# List all folders
Source: https://dub.co/docs/api-reference/folders/list

get /folders
Retrieve a paginated list of folders for the authenticated workspace.



# Update a folder
Source: https://dub.co/docs/api-reference/folders/update

patch /folders/{id}
Update a folder in the workspace.



# Introduction
Source: https://dub.co/docs/api-reference/introduction

Learn how to use Dub's API to programmatically manage resources in your Dub workspace.

Dub's API let you manage your Dub resources programmatically.

You can use the API to:

* [Create links](/docs/api-reference/links/create)
* [Create partners](/docs/api-reference/partners/create)
* Track [lead](/docs/api-reference/track/lead) and [sale](/docs/api-reference/track/sale) events
* [Retrieve analytics](/docs/api-reference/analytics/retrieve)

## Base URL

Dub's API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported.

The Base URL for all API endpoints is:

```bash Terminal theme={null}
https://api.dub.co
```

## Authentication

Authentication to Dub's API is performed via the Authorization header with a Bearer token. To authenticate, you need to include the Authorization header with the word `Bearer` followed by your [API key](/docs/api-reference/authentication#api-keys) in your requests like so:

```bash Terminal theme={null}
Authorization: Bearer dub_xxxxxx
```

Here are examples of how to authenticate with Dub's API in different programming languages:

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/links \
    --header 'Authorization: Bearer dub_xxxxxx'
  ```

  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  const dub = new Dub({
    token: "dub_xxxxxx",
  });

  // Make API calls
  const links = await dub.links.list();
  ```

  ```python Python theme={null}
  from dub import Dub

  client = Dub(api_key="dub_xxxxxx")

  # Make API calls
  links = client.links.list()
  ```

  ```go Go theme={null}
  import (
    "context"
    "github.com/dubinc/dub-go"
  )

  client := dub.NewClient("dub_xxxxxx")

  // Make API calls
  ctx := context.Background()
  links, err := client.Links.List(ctx)
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  client = Dub::Client.new(api_key: "dub_xxxxxx")

  # Make API calls
  links = client.links.list
  ```

  ```php PHP theme={null}
  use Dub\Client;

  $client = new Client([
      'api_key' => 'dub_xxxxxx'
  ]);

  // Make API calls
  $links = $client->links->list();
  ```
</CodeGroup>

Learn more about [how to get your API key](/docs/api-reference/authentication#api-keys).

## Native SDKs

Dub offers native SDKs in some of the most popular programming languages:

* [TypeScript SDK](/docs/sdks/typescript)
* [Python SDK](/docs/sdks/python)
* [Ruby SDK](/docs/sdks/ruby)
* [PHP SDK](/docs/sdks/php)
* [Go SDK](/docs/sdks/go)

You can find the full list of SDKs [here](/docs/sdks/overview).

## Error handling

Dub API returns machine readable error codes, human readable error messages and a link to the docs for more information.

Here is how an error response looks like:

```json theme={null}
{
  "error": {
    "code": "not_found",
    "message": "The requested resource was not found.",
    "doc_url": "https://dub.co/docs/api-reference/errors#not-found"
  }
}
```

Here is a list of all error codes Dub API returns:

<AccordionGroup>
  <Accordion title="bad_request ">
    * **Status:** 400
    * **Problem:** The request is malformed, either missing required fields, using wrong datatypes, or being syntactically incorrect.
    * **Solution:** Check the request and make sure it is properly formatted.
  </Accordion>

  <Accordion title="unauthorized">
    * **Status:** 401
    * **Problem:** The request has not been applied because it lacks valid authentication credentials for the target resource.
    * **Solution:** Make sure you are using the correct API key or access token.
  </Accordion>

  <Accordion title="forbidden">
    * **Status:** 403
    * **Problem:** The server understood the request, but is refusing to fulfill it because the client lacks proper permission.
    * **Solution:** Make sure you have the necessary permissions to access the resource.
  </Accordion>

  <Accordion title="not_found">
    * **Status:** 404
    * **Problem:** The server has not found anything matching the request URI.
    * **Solution:** Check the request and make sure the resource exists.
  </Accordion>

  <Accordion title="conflict">
    * **Status:** 409
    * **Problem:** Another resource already uses the same identifier. For example, workspace slug must be unique.
    * **Solution:** Change the identifier to a unique value.
  </Accordion>

  <Accordion title="invite_expired">
    * **Status:** 410
    * **Problem:** The invite has expired.
    * **Solution:** Generate a new invite.
  </Accordion>

  <Accordion title="unprocessable_entity">
    * **Status:** 422
    * **Problem:** The server was unable to process the request because it contains invalid data.
    * **Solution:** Check the request and make sure input data is valid.
  </Accordion>

  <Accordion title="rate_limit_exceeded">
    * **Status:** 429
    * **Problem:** The request has been rate limited.
    * **Solution:** Wait for a while and try again.
  </Accordion>

  <Accordion title="internal_server_error">
    * **Status:** 500
    * **Problem:** The server encountered an unexpected condition that prevented it from fulfilling the request.
    * **Solution:** Try again later. If the problem persists, contact support.
  </Accordion>
</AccordionGroup>

## Pagination

Dub's API supports pagination. This is useful when you have a large number of resources and you want to retrieve them in smaller chunks.

These list API methods share a common set of parameters that allow you to control the number of items returned and the page number. For example, you can:

* [retrieve a list of links](/docs/api-reference/links/list)
* [retrieve a list of domains](/docs/api-reference/domains/create)
* [retrieve a list of events](/docs/api-reference/events/list)

### Parameters

<ParamField type="string">
  The page number to retrieve. By default, the first page is returned.
</ParamField>

<ParamField type="string">
  The number of items to retrieve per page. The default value varies by
  endpoint. Maximum value is 100.
</ParamField>

<ParamField type="string">
  The field to sort the results by.
</ParamField>

<ParamField type="string">
  The order to sort the results by. Can be `asc` or `desc`.
</ParamField>

### Example

The following example demonstrates how to retrieve the first page of 10 links:

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/links?page=1&pageSize=10 \
    --header 'Authorization: Bearer <token>'
  ```

  ```javascript Node.js theme={null}
  const res = await dub.links.list({
    page: 1,
    pageSize: 10,
  });
  ```

  ```python Python theme={null}
  res = s.links.list(request={
    "page": 1,
    "page_size": 10,
  })
  ```

  ```go Go theme={null}
  request := operations.GetLinksRequest{
    Page: dubgo.Float64(1),
    PageSize: dubgo.Float64(10),
  }

  ctx := context.Background()
  res, err := s.Links.List(ctx, request)
  ```

  ```ruby Ruby theme={null}
  req = ::OpenApiSDK::Operations::GetLinksRequest.new(
    page: 1,
    page_size: 10,
  )

  res = s.links.list(req)
  ```
</CodeGroup>


# Bulk create links
Source: https://dub.co/docs/api-reference/links/bulk-create

post /links/bulk
Bulk create up to 100 links for the authenticated workspace.

<Warning>
  We currently do not send [webhook events](/docs/webhooks/introduction) for
  bulk link creation.
</Warning>


# Bulk delete links
Source: https://dub.co/docs/api-reference/links/bulk-delete

delete /links/bulk
Bulk delete up to 100 links for the authenticated workspace.

<Warning>
  This is a destructive action and cannot be undone. Proceed with caution. Also,
  [webhook events](/docs/webhooks/introduction) will not be triggered when using
  this endpoint.
</Warning>


# Bulk update links
Source: https://dub.co/docs/api-reference/links/bulk-update

patch /links/bulk
Bulk update up to 100 links with the same data for the authenticated workspace.

This endpoint lets you update up to 100 links **with the same data**.

Some potential use cases:

* Tagging multiple links at once
* Setting the same expiration date for multiple links
* Updating UTM parameters for multiple links

<Warning>
  You cannot update the domain or key of a link with this endpoint. Also,
  [webhook events](/docs/webhooks/introduction) will not be triggered when using
  this endpoint.
</Warning>


# Retrieve links count
Source: https://dub.co/docs/api-reference/links/count

get /links/count
Retrieve the number of links for the authenticated workspace.



# Create a link
Source: https://dub.co/docs/api-reference/links/create

post /links
Create a link for the authenticated workspace.



# Delete a link
Source: https://dub.co/docs/api-reference/links/delete

delete /links/{linkId}
Delete a link for the authenticated workspace.



# List all links
Source: https://dub.co/docs/api-reference/links/list

get /links
Retrieve a paginated list of links for the authenticated workspace.



# Retrieve a link
Source: https://dub.co/docs/api-reference/links/retrieve

get /links/info
Retrieve the info for a link.

You can retrieve a link by providing one of the following as a query parameter:

* `domain` and `key`.
* `linkId`.
* `externalId`.


# Update a link
Source: https://dub.co/docs/api-reference/links/update

patch /links/{linkId}
Update a link for the authenticated workspace. If there's no change, returns it as it is.



# Upsert a link
Source: https://dub.co/docs/api-reference/links/upsert

put /links/upsert
Upsert a link for the authenticated workspace by its URL. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.



# Approve a partner application
Source: https://dub.co/docs/api-reference/partners/applications/approve

post /partners/applications/approve
Approve a pending partner application to your program. The partner will be enrolled in the specified group and notified of the approval.



# List all pending partner applications
Source: https://dub.co/docs/api-reference/partners/applications/list

get /partners/applications
Retrieve a paginated list of pending applications for your partner program.



# Reject a partner application
Source: https://dub.co/docs/api-reference/partners/applications/reject

post /partners/applications/reject
Reject a pending partner application to your program. The partner will be notified via email that their application was not approved.



# Ban a partner
Source: https://dub.co/docs/api-reference/partners/ban

post /partners/ban
Ban a partner from your program. This will disable all links and mark all commissions as canceled.



# Create or update a partner
Source: https://dub.co/docs/api-reference/partners/create

post /partners
Creates or updates a partner record (upsert behavior). If a partner with the same email already exists, their program enrollment will be updated with the provided tenantId. If no existing partner is found, a new partner will be created using the supplied information.



# Create a link for a partner
Source: https://dub.co/docs/api-reference/partners/create-link

post /partners/links
Create a link for a partner that is enrolled in your program.



# Deactivate a partner
Source: https://dub.co/docs/api-reference/partners/deactivate

post /partners/deactivate
This will deactivate the partner from your program and disable all their active links. Their commissions and payouts will remain intact. You can reactivate them later if needed.



# List all partners
Source: https://dub.co/docs/api-reference/partners/list

get /partners
List all partners for a partner program.



# Retrieve a partner's links.
Source: https://dub.co/docs/api-reference/partners/retrieve-links

get /partners/links
Retrieve a partner's links by their partner ID or tenant ID.



# Upsert a link for a partner
Source: https://dub.co/docs/api-reference/partners/upsert-link

put /partners/links/upsert
Upsert a link for a partner that is enrolled in your program. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.



# List all payouts
Source: https://dub.co/docs/api-reference/payouts/list

get /payouts
Retrieve a paginated list of payouts for your partner program.

<Note>
  Payouts endpoints require an [Business plan](https://dub.co/pricing/partners)
  subscription or higher.
</Note>


# Rate limits
Source: https://dub.co/docs/api-reference/rate-limits

Learn about Dub's API rate limits.

Dub's API rate limiting is in conformance with the [IETF standard](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers):

| Header Name             | Description                                                                     |
| ----------------------- | ------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | The maximum number of requests that the consumer is permitted to make per hour. |
| `X-RateLimit-Remaining` | The number of requests remaining in the current rate limit window.              |
| `X-RateLimit-Reset`     | The time at which the current rate limit window resets in UTC epoch seconds.    |
| `Retry-After`           | The number of seconds to wait before retrying the request again.                |

Dub's API is capped at **60 requests per minute** per key on the Free plan, with elevated limits for [Pro plan](https://dub.co/pricing) and above.

This is implemented to ensure a fair usage policy so that excessive use by a single user does not adversely affect the performance and usage of the API by others.

You'll receive a `429 Too Many Requests` response code if the rate limit is exceeded.

## Rate limits by plan

Depending on your Dub plan, you can expect the following rate limits:

| Plan                                    | Rate limit                                                              |
| --------------------------------------- | ----------------------------------------------------------------------- |
| Free                                    | 60 requests per minute                                                  |
| [Pro](https://dub.co/pricing)           | 600 requests per minute                                                 |
| [Business](https://dub.co/pricing)      | 1,200 requests per minute                                               |
| [Advanced](https://dub.co/pricing)      | 3,000 requests per minute                                               |
| [Enterprise](https://dub.co/enterprise) | Custom – [reach out to sales](https://dub.co/contact/sales) for details |

## Rate limits by endpoint

Dub's [analytics endpoints](/docs/concepts/analytics/introduction) have separate, per-second rate limits to ensure optimal performance:

| Plan                                    | Analytics API rate limit                                                |
| --------------------------------------- | ----------------------------------------------------------------------- |
| Free                                    | N/A                                                                     |
| [Pro](https://dub.co/pricing)           | 2 requests per second                                                   |
| [Business](https://dub.co/pricing)      | 4 requests per second                                                   |
| [Advanced](https://dub.co/pricing)      | 8 requests per second                                                   |
| [Enterprise](https://dub.co/enterprise) | Custom – [reach out to sales](https://dub.co/contact/sales) for details |

Analytics endpoints include:

* [`GET /analytics`](/docs/api-reference/analytics/retrieve) – retrieving aggregated analytics
* [`GET /events`](/docs/api-reference/events/list) – retrieving paginated lists of events

All other API endpoints use the standard rate limits listed in the [Rate limits by plan](#rate-limits-by-plan) section above.

## How to comply with rate limits

Here's how you can optimize your API setup to comply with rate limits:

### 1. Bulk link creation

If you need to create a lot of links within a short period of time, try our [bulk link creation endpoint](/docs/api-reference/links/bulk-create) instead (create up to 100 links in one API call)

<CodeGroup>
  ```javascript Node.js theme={null}
  await dub.links.createMany([
    {
      url: "https://google.com",
    },
    {
      url: "https://twitter.com",
    },
    {
      url: "https://linkedin.com",
    },
  ]);
  ```

  ```python Python theme={null}
  res = d.links.create_many(request=[
    {
      url: "https://google.com",
    },
    {
      url: "https://twitter.com",
    },
    {
      url: "https://linkedin.com",
    },
  ]);
  ```

  ```go Go theme={null}
  var request []operations.RequestBody =
    []operations.RequestBody{
      operations.RequestBody{
          URL: "https://google.com",
      },
      operations.RequestBody{
        URL: "https://twitter.com",
      },
      operations.RequestBody{
        URL: "https://linkedin.com",
      },
    }
  ctx := context.Background()
  res, err := s.Links.CreateMany(ctx, request)
  ```

  ```ruby Ruby theme={null}
  s.links.create_many(
    ::OpenApiSDK::Operations::BulkCreateLinksRequest.new(
      request_body: [
        ::OpenApiSDK::Operations::RequestBody.new(
          url: "https://google.com",
        ),
        ::OpenApiSDK::Operations::RequestBody.new(
          url: "https://twitter.com",
        ),
        ::OpenApiSDK::Operations::RequestBody.new(
          url: "https://linkedin.com",
        ),
      ]
    )
  )
  ```
</CodeGroup>

### 2. Fetch workspace-level analytics

If you're using our [Analytics API](/docs/api-reference/analytics/retrieve), instead of retrieving the clicks count for every single link, try making a single API call to get workspace-level click analytics instead.

<CodeGroup>
  ```javascript Node.js theme={null}
  await dub.analytics.retrieve({
    groupBy: "top_links",
    start: "4 hours ago", // we support natural language for start/end params
  });
  ```

  ```python Python theme={null}
  res = d.analytics.retrieve(request={
      "groupBy": "top_links",
      "start": "4 hours ago", // we support natural language for start/end params
  })
  ```

  ```go Go theme={null}
  func main() {
  	// Retrieve the timeseries analytics for the last 7 days for a link
  	request := operations.RetrieveAnalyticsRequest{
          GroupBy: "top_links",
          Start: "4 hours ago", // we support natural language for start/end params
  	}

  	ctx := context.Background()
  	res, err := d.Analytics.Retrieve(ctx, request)
  	if err != nil {
  		log.Fatal(err)
  	}
  	if res.OneOf != nil {
  		// handle response
  	}
  }
  ```

  ```ruby Ruby theme={null}
  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    group_by: ::OpenApiSDK::Operations::GroupBy::TOP_LINKS,
    start: "4 hours ago", // we support natural language for start/end params
  )

  res = dub.analytics.retrieve(req)

  puts res.raw_response.body
  ```
</CodeGroup>

### 3. Leverage webhooks

If you're expecting high volume, fast-changing data (e.g. thousands of clicks on your links per day) and would prefer to store the data on your end, we recommend using our [real-time webhooks feature](https://dub.co/blog/introducing-webhooks) instead.

<Frame>
  <img alt="Webhook event logs" />
</Frame>

[Webhooks](/docs/webhooks/introduction) are *push-based*, meaning that Dub will send events to your webhook receiver endpoint when specific events occur, while a REST API is *pull-based*, meaning that you need to consistently poll the API endpoint to get real-time updates.

Check out our [webhooks documentation](/docs/webhooks/introduction) to learn more.


# Create a tag
Source: https://dub.co/docs/api-reference/tags/create

post /tags
Create a tag for the authenticated workspace.



# List all tags
Source: https://dub.co/docs/api-reference/tags/list

get /tags
Retrieve a paginated list of tags for the authenticated workspace.



# Update a tag
Source: https://dub.co/docs/api-reference/tags/update

patch /tags/{id}
Update a tag in the workspace.



# Track a lead event
Source: https://dub.co/docs/api-reference/track/lead

post /track/lead
Track a lead for a short link.

<Note>
  Conversion tracking requires a [Business
  plan](https://dub.co/pricing/partners) subscription or higher.
</Note>

### Deduplication behavior

Lead events are automatically deduplicated to prevent duplicate tracking:

* Events are deduplicated based on the combination of `customerExternalId` (the user ID of the referred customer within your database) and `eventName`
* If you send multiple lead events with the same customer ID and event name, only the first event will be tracked
* Subsequent duplicate events will return `null` and won't be tracked


# Track a deep link open event
Source: https://dub.co/docs/api-reference/track/open

post /track/open
This endpoint is used to track when a user opens your app via a Dub-powered deep link (for both iOS and Android).

<Note>
  Deferred deep linking requires a [Pro plan](https://dub.co/pricing/links)
  subscription or higher.
</Note>


# Track a sale event
Source: https://dub.co/docs/api-reference/track/sale

post /track/sale
Track a sale for a short link.

<Note>
  Conversion tracking requires a [Business
  plan](https://dub.co/pricing/partners) subscription or higher.
</Note>


# Device data
Source: https://dub.co/docs/concepts/analytics/device



<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>

Device data allows you to analyze how users interact with your links across different devices, browsers, and operating systems.

## Device analytics

The top devices by event count, including device names.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "devices",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "devices",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'devices',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("devices"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "devices",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Browser analytics

The top browsers by event count, including browser names.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "browsers",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "browsers",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'browsers',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("browsers"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "browsers",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Operating system analytics

The top operating systems by event count, including OS names.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "os",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "os",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'os',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("os"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "os",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>


# Introduction
Source: https://dub.co/docs/concepts/analytics/introduction

Learn how to use Dub's Analytics API to build real-time analytics dashboards for your links.

<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>

Dub's [Analytics API](/docs/api-reference/analytics/retrieve) allows you to build real-time analytics dashboards for your links that lives directly inside your application.

## Fundamentals

At a high level, Dub's Analytics API allows you to retrieve data about your links by event type and group by different dimensions.

Dub's Analytics API supports the following `event` types:

* `clicks` – the number of clicks on a link
* `leads` – the number of leads generated from a link
* `sales` – the number of sales generated from a link

Each of these events can be aggregated in different ways by using the `groupBy` parameter:

<AccordionGroup>
  <Accordion title="count">
    The total number of events over the specified time period.

    Example response:

    ```json theme={null}
    {
      "clicks": 100,
      "leads": 5,
      "sales": 2,
      "saleAmount": 5000
    }
    ```
  </Accordion>

  <Accordion title="timeseries">
    The number of events over a given time interval, broken down by time periods.

    Example response:

    ```json theme={null}
    [
      {
        "start": "2024-01-01T00:00:00.000Z",
        "clicks": 10,
        "leads": 1,
        "sales": 0,
        "saleAmount": 0
      },
      {
        "start": "2024-01-02T00:00:00.000Z",
        "clicks": 15,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="top_links">
    The top links by event count, including link metadata.

    Example response:

    ```json theme={null}
    [
      {
        "id": "clux0rgak00011...",
        "domain": "dub.co",
        "key": "github",
        "shortLink": "https://dub.co/github",
        "url": "https://github.com",
        "title": "GitHub",
        "createdAt": "2024-01-01T00:00:00.000Z",
        "clicks": 50,
        "leads": 3,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="countries">
    The top countries by event count, using ISO 3166-1 alpha-2 country codes.

    Example response:

    ```json theme={null}
    [
      {
        "country": "US",
        "region": "*",
        "city": "*",
        "clicks": 30,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="cities">
    The top cities by event count, including city names and ISO 3166-1 alpha-2 country codes.

    Example response:

    ```json theme={null}
    [
      {
        "country": "US",
        "region": "CA",
        "city": "San Francisco",
        "clicks": 20,
        "leads": 1,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="regions">
    The top regions by event count, including region codes and ISO 3166-1 alpha-2 country codes.

    Example response:

    ```json theme={null}
    [
      {
        "country": "US",
        "region": "CA",
        "city": "*",
        "clicks": 25,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="continents">
    The top continents by event count, using 2-letter ISO continent codes.

    Example response:

    ```json theme={null}
    [
      {
        "continent": "NA",
        "clicks": 40,
        "leads": 3,
        "sales": 2,
        "saleAmount": 5000
      }
    ]
    ```
  </Accordion>

  <Accordion title="devices">
    The top devices by event count, including device names.

    Example response:

    ```json theme={null}
    [
      {
        "device": "iPhone",
        "clicks": 35,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="browsers">
    The top browsers by event count, including browser names.

    Example response:

    ```json theme={null}
    [
      {
        "browser": "Chrome",
        "clicks": 45,
        "leads": 3,
        "sales": 2,
        "saleAmount": 5000
      }
    ]
    ```
  </Accordion>

  <Accordion title="os">
    The top operating systems by event count, including OS names.

    Example response:

    ```json theme={null}
    [
      {
        "os": "iOS",
        "clicks": 30,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="referrers">
    The top referrers by event count, including referrer names.

    Example response:

    ```json theme={null}
    [
      {
        "referer": "Google",
        "clicks": 25,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="referer_urls">
    The top referrer URLs by event count, including full URLs.

    Example response:

    ```json theme={null}
    [
      {
        "refererUrl": "https://www.google.com",
        "clicks": 20,
        "leads": 1,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="utm_sources">
    The top UTM sources by event count.

    Example response:

    ```json theme={null}
    [
      {
        "utm_source": "newsletter",
        "clicks": 25,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="utm_mediums">
    The top UTM mediums by event count.

    Example response:

    ```json theme={null}
    [
      {
        "utm_medium": "email",
        "clicks": 20,
        "leads": 1,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="utm_campaigns">
    The top UTM campaigns by event count.

    Example response:

    ```json theme={null}
    [
      {
        "utm_campaign": "summer_sale",
        "clicks": 30,
        "leads": 2,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="utm_terms">
    The top UTM terms by event count.

    Example response:

    ```json theme={null}
    [
      {
        "utm_term": "discount",
        "clicks": 15,
        "leads": 1,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>

  <Accordion title="utm_contents">
    The top UTM contents by event count.

    Example response:

    ```json theme={null}
    [
      {
        "utm_content": "banner",
        "clicks": 20,
        "leads": 1,
        "sales": 1,
        "saleAmount": 2500
      }
    ]
    ```
  </Accordion>
</AccordionGroup>

## Example queries

Here are some examples of how to retrieve data using Dub's [Analytics API](/docs/api-reference/analytics/retrieve):

* [Total event count](#total-event-count)
* [Timeseries data](#timeseries-data)
* [Top links by event](#top-links-by-event)

### Total event count

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "count",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "count",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'count',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("count"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "count",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

### Timeseries data

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "timeseries",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "timeseries",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'timeseries',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("timeseries"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "timeseries",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

### Top links by event

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "top_links",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "top_links",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'top_links',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("top_links"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "top_links",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Example apps

With Dub's [Analytics API](/docs/api-reference/analytics/retrieve), you can build user-facing analytics dashboards with the real-time click and conversion data for your links.

Here are some open-source examples of how you can use the Analytics API to build your own custom analytics dashboards:

<CardGroup>
  <Card title="OSS Gallery" icon="github" href="https://github.com/dubinc/oss-gallery">
    Programmatically shorten links and fetch real-time click analytics with Dub
  </Card>

  <Card title="Cap.so" icon="github" href="https://github.com/CapSoftware/Cap/blob/2d619ca7ad19247657cf059e10ac74e02f03ef04/apps/web/actions/videos/get-analytics.ts">
    How Cap.so fetches real-time click analytics for their recording links
  </Card>
</CardGroup>

And here's another [real-world example](https://x.com/meetassembly/status/1901691081579794505) of a custom analytics dashboard built with the Analytics API:

<Frame>
  <img alt="Assembly link analytics" />
</Frame>


# Links data
Source: https://dub.co/docs/concepts/analytics/links

Learn how to retrieve analytics data on Dub by links, folders, and tags

<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>

Links data allows you to analyze how individual links and link collections are performing across your workspace.

## Link analytics

The top links by event count.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_links",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_links",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_links',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_links"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_links",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

You can also filter analytics for a specific link (or set of links) by passing the `linkId` parameter. It supports advanced filtering: single value, multiple values (comma-separated), or exclusion (prefix with `-`).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_links",
    linkId: "-fold_xxx...",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_links",
          "linkId": "-fold_xxx...",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_links',
      linkId: '-fold_xxx...',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_links"),
          LinkID: dubgo.String("-fold_xxx..."),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_links",
    linkId: "-fold_xxx...",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Link folder analytics

The top link folders by event count, including folder names and IDs.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_folders",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_folders",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_folders',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_folders"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_folders",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

You can also filter analytics for a specific folder (or set of folders) by passing the `folderId` parameter. It supports advanced filtering: single value, multiple values (comma-separated), or exclusion (prefix with `-`).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_links",
    folderId: "fold_xxx...",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_links",
          "folderId": "fold_xxx...",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_links',
      folderId: 'fold_xxx...',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_links"),
          FolderID: dubgo.String("fold_xxx..."),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_links",
    folderId: "fold_xxx...",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Link tag analytics

The top link tags by event count, including tag names and IDs.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_link_tags",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_link_tags",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_link_tags',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_link_tags"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_link_tags",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

You can also filter analytics by one or more tags by passing the `tagIds` parameter. It supports advanced filtering: single value, multiple values, or exclusion (prefix with `-`).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_links",
    tagId: ["tag_12345", "-tag_67890"],
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_links",
          "tagIds": ["tag_12345", "-tag_67890"],
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_links',
      tagId: ['tag_12345', '-tag_67890'],
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_links"),
          TagID: []string{"tag_12345", "-tag_67890"},
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_links",
    tagId: ["tag_12345", "-tag_67890"],
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>


# Location data
Source: https://dub.co/docs/concepts/analytics/location



<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>

Location data allows you to analyze how users interact with your links across different geographical locations.

## Country analytics

The top countries by event count, including ISO 3166-1 alpha-2 country codes.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "countries",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "countries",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'countries',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("countries"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "countries",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## City analytics

The top cities by event count, including city names and their corresponding region codes + ISO 3166-1 alpha-2 country codes.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "cities",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "cities",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'cities',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("cities"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "cities",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Continent analytics

The top continents by event count, using 2-letter ISO continent codes.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "continents",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "continents",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'continents',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("continents"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "continents",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Region analytics

The top regions by event count, including region codes and ISO 3166-1 alpha-2 country codes.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "regions",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "regions",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'regions',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("regions"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "regions",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>


# Partners data
Source: https://dub.co/docs/concepts/analytics/partners

Learn how to retrieve analytics data on Dub by partners, partner groups, and partner tags

<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher. Partner-related analytics also require an active [Dub
  Partners](https://dub.co/partners) program.
</Note>

Partner data allows you to analyze how your partners, partner groups, and partner tags are driving clicks, leads, and sales across your program.

## Partner analytics

The top partners by event count, including partner names and IDs.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_partners",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_partners",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_partners',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_partners"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_partners",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

You can also filter analytics for a specific partner (or set of partners) by passing the `partnerId` parameter. It supports advanced filtering: single value, multiple values (comma-separated), or exclusion (prefix with `-`).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_links",
    partnerId: "pn_123",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_links",
          "partnerId": "pn_123",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_links',
      partnerId: 'pn_123',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_links"),
          PartnerID: dubgo.String("pn_123"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_links",
    partnerId: "pn_123",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Partner group analytics

The top partner groups by event count, including group names and IDs.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_groups",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_groups",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_groups',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_groups"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_groups",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

You can also filter analytics by one or more partner groups by passing the `groupId` parameter. It supports advanced filtering: single value, multiple values (comma-separated), or exclusion (prefix with `-`).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_partners",
    groupId: "grp_123",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_partners",
          "groupId": "grp_123",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_partners',
      groupId: 'grp_123',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_partners"),
          GroupID: dubgo.String("grp_123"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_partners",
    groupId: "grp_123",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Partner tag analytics

The top partner tags by event count, including tag names and IDs.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_partner_tags",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_partner_tags",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_partner_tags',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_partner_tags"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_partner_tags",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

You can also filter analytics by one or more partner tags by passing the `partnerTagId` parameter. It supports advanced filtering: single value, multiple values (comma-separated), or exclusion (prefix with `-`).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "sales",
    groupBy: "top_partners",
    partnerTagId: "ptag_123",
    interval: "30d",
    // saleType: "new" // Optional: filter by new or recurring sales
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "sales",
          "groupBy": "top_partners",
          "partnerTagId": "ptag_123",
          "interval": "30d",
          # "saleType": "new",  # Optional: filter by new or recurring sales
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'sales',
      groupBy: 'top_partners',
      partnerTagId: 'ptag_123',
      interval: '30d',
      // saleType: 'new', // Optional: filter by new or recurring sales
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("sales"),
          GroupBy: dubgo.String("top_partners"),
          PartnerTagID: dubgo.String("ptag_123"),
          Interval: dubgo.String("30d"),
          // SaleType: dubgo.String("new"), // Optional: filter by new or recurring sales
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "sales",
    groupBy: "top_partners",
    partnerTagId: "ptag_123",
    interval: "30d",
    # saleType: "new", # Optional: filter by new or recurring sales
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>


# Referrers data
Source: https://dub.co/docs/concepts/analytics/referrers



<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>

Referrers data allows you to analyze where your link traffic is coming from, including both the referrer domain and the full referrer URL.

## Referrer domain analytics

The top referrers by event count, including referrer names.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "referrers",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "referrers",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'referrers',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("referrers"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "referrers",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## Referrer URL analytics

The top referrer URLs by event count, including full URLs.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "referer_urls",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "referer_urls",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'referer_urls',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("referer_urls"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "referer_urls",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>


# UTM data
Source: https://dub.co/docs/concepts/analytics/utm



<Note>
  Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription
  or higher.
</Note>

UTM data allows you to analyze the effectiveness of your marketing campaigns by tracking the source, medium, campaign, term, and content parameters.

## UTM source analytics

The top UTM sources by event count.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "utm_sources",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "utm_sources",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'utm_sources',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("utm_sources"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "utm_sources",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## UTM medium analytics

The top UTM mediums by event count.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "utm_mediums",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "utm_mediums",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'utm_mediums',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("utm_mediums"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "utm_mediums",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## UTM campaign analytics

The top UTM campaigns by event count.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "utm_campaigns",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "utm_campaigns",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'utm_campaigns',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("utm_campaigns"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "utm_campaigns",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## UTM term analytics

The top UTM terms by event count.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "utm_terms",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "utm_terms",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'utm_terms',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("utm_terms"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "utm_terms",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>

## UTM content analytics

The top UTM contents by event count.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.analytics.retrieve({
    event: "clicks",
    groupBy: "utm_contents",
    linkId: "clux0rgak00011...",
    interval: "30d",
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.analytics.retrieve(request={
          "event": "clicks",
          "groupBy": "utm_contents",
          "linkId": "clux0rgak00011...",
          "interval": "30d",
      })
      print(res)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\RetrieveAnalyticsRequest(
      event: 'clicks',
      groupBy: 'utm_contents',
      linkId: 'clux0rgak00011...',
      interval: '30d',
  );

  $response = $sdk->analytics->retrieve(
      request: $request
  );

  if ($response->oneOf !== null) {
      // handle response
  }
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
          Event: dubgo.String("clicks"),
          GroupBy: dubgo.String("utm_contents"),
          LinkID: dubgo.String("clux0rgak00011..."),
          Interval: dubgo.String("30d"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // handle response
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
        security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
        ),
      )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    event: "clicks",
    groupBy: "utm_contents",
    linkId: "clux0rgak00011...",
    interval: "30d",
  )

  res = s.analytics.retrieve(req)

  if ! res.one_of.nil?
    # handle response
  end
  ```
</CodeGroup>


# How attribution works
Source: https://dub.co/docs/concepts/attribution

A deep-dive into Dub's attribution infrastructure and how conversion tracking works from click → lead → sale → commission.

## The attribution flow

Dub uses a multi-stage attribution model that tracks the complete customer journey from initial click to final purchase:

* [Stage 1: Click tracking](#stage-1-click-tracking)
* [Stage 2: Lead tracking](#stage-2-lead-tracking)
* [Stage 3: Sale tracking](#stage-3-sale-tracking)

### Stage 1: Click tracking

When a visitor clicks a partner's referral link, Dub captures the click **server-side** before redirecting to your website. This happens automatically and cannot be blocked by ad blockers.

<Frame>
  <img alt="Diagram showing how click events are tracked" />
</Frame>

**Data captured at click:**

* Unique click ID
* Timestamp
* Geographic location (country, city, continent)
* Device type (mobile, desktop, tablet)
* Browser and operating system
* Referrer URL
* UTM parameters
* Whether the click came from a QR code or a short link

The click is appended to your destination URL as a `dub_id` query parameter:

```
https://yoursite.com?dub_id=cm3w8x2...
```

Then, the [Dub Analytics script](/docs/sdks/client-side/introduction) detects the `dub_id` parameter and stores it as a **first-party cookie**.

```javascript theme={null}
// The SDK automatically handles this when installed
// Cookie: dub_id=xyz... (expires in 90 days by default)
```

**Why first-party cookies?**

* Persist across page navigation and sessions
* Not blocked by ad blockers (unlike third-party cookies)
* Compliant with privacy regulations
* Work reliably across all browsers

The default cookie lifetime is **90 days**, meaning conversions within this window are attributed to the original click. You can customize this with the [`expiresInDays` parameter](/docs/sdks/client-side/introduction#param-expires-in-days).

### Stage 2: Lead tracking

<Frame>
  <img alt="Diagram showing how lead events are tracked" />
</Frame>

When the visitor takes a qualifying action (signup, booking, form submission), you track a **lead event**. This links the customer to the original click.

```typescript theme={null}
await dub.track.lead({
  clickId: cookies.get("dub_id"), // From the cookie
  eventName: "Sign Up",
  customerExternalId: user.id, // Your user ID
  customerEmail: user.email,
  customerName: user.name,
});
```

The lead event is [automatically deduplicated](/docs/api-reference/track/lead#deduplication-behavior) based on `customerExternalId` + `eventName`. Only the first event for each combination is recorded, preventing duplicate attribution.

### Stage 3: Sale tracking

<Frame>
  <img alt="Diagram showing how sale events are tracked" />
</Frame>

When the customer makes a purchase, you track a **sale event**. Dub automatically links this to the customer's previous lead event.

```typescript theme={null}
await dub.track.sale({
  customerExternalId: user.id, // Same ID from lead event
  amount: 9900, // Amount in cents ($99.00)
  paymentProcessor: "stripe",
  invoiceId: "inv_123", // For idempotency
});
```

The sale is attributed to the partner who drove the original click, and a **commission record** is automatically created based on your program's reward rules.

## Attribution models

Dub supports two attribution models:

1. [Last-click attribution (default)](#last-click-attribution-default)
2. [First-click attribution](#first-click-attribution)

### Last-click attribution (default)

All credit goes to the **most recent** partner link the customer clicked before converting.

<CodeGroup>
  ```javascript HTML theme={null}
  // include this script tag in your HTML Head tag
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    data-attribution-model="last-click" // can be omitted since it's the default
    defer
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // install the package (e.g. npm install @dub/analytics)
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          attributionModel="last-click" // can be omitted since it's the default
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```
</CodeGroup>

<Tip>
  Pro-tip: Last-click attribution is the most commonly used attribution model
  when it comes to affiliate attribution, since it gives the most credit to the
  final partner that got the customer to convert.
</Tip>

### First-click attribution

All credit goes to the **first** partner who introduced the customer, regardless of subsequent clicks.

<CodeGroup>
  ```javascript HTML theme={null}
  // include this script tag in your HTML Head tag
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    data-attribution-model="first-click"
    defer
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // install the package (e.g. npm install @dub/analytics)
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          attributionModel="first-click"
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```
</CodeGroup>

When a customer clicks multiple partner links:

| Model       | Behavior                                                                                     |
| ----------- | -------------------------------------------------------------------------------------------- |
| Last-click  | The `dub_id` cookie is overwritten with each new click. The most recent partner gets credit. |
| First-click | The original `dub_id` cookie is preserved. The first partner retains credit.                 |

Each click is still recorded in analytics, so you can see the full customer journey even if only one partner receives the commission.

## Attribution window

The **attribution window** is the timeframe during which a conversion can be credited to a click. In Dub, this is controlled by the cookie lifetime:

| Configuration   | Default | Description                       |
| --------------- | ------- | --------------------------------- |
| `expiresInDays` | 90      | Days the `dub_id` cookie persists |

<CodeGroup>
  ```javascript HTML theme={null}
  // Set a 30-day attribution window
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    data-cookie-options='{"expiresInDays": 30}'
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // install the package (e.g. npm install @dub/analytics)
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          cookieOptions={{
            expiresInDays: 30,
          }}
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```
</CodeGroup>

If the customer converts after the cookie expires, the conversion event will not be attributed to the original click.

## Cross-domain attribution

If your customer journey spans multiple domains, Dub supports [cross-domain tracking](/docs/sdks/client-side/features/cross-domain-tracking) as well:

* `yoursite.com` → `app.yoursite.com`
* `app.yoursite.com` → `checkout.yoursite.com`
* `yoursite.com` → `anothersite.com`

Learn more in the [cross-domain tracking guide](/docs/sdks/client-side/features/cross-domain-tracking).

## Partner commission flow

When a sale is attributed, Dub automatically calculates and records the partner's commission:

1. **Sale event received** with `customerExternalId`
2. **Customer lookup** finds the associated lead and original click
3. **Partner identified** from the click's referral link
4. **Commission calculated** based on the [reward rules](/help/article/partner-rewards) for the [partner's group](/help/article/partner-groups)
5. **Commission created** with [`pending` status](/help/article/partner-commissions#commission-statuses)

## Direct sale attribution

For scenarios without a signup flow (e.g., one-time purchases), you can track sales directly with the click ID:

```typescript theme={null}
await dub.track.sale({
  clickId: cookies.get("dub_id"), // Directly from cookie
  customerExternalId: order.email,
  customerName: order.name,
  customerEmail: order.email,
  amount: 4900,
  invoiceId: order.id,
});
```

Learn more about [direct sale tracking](/docs/conversions/sales/direct).

<Note>
  Direct sale tracking bypasses the lead event. This means lead-based rewards
  are not created—only sale commissions.
</Note>

## Deferred lead tracking

For products with qualification periods (trials, approvals), Dub supports **deferred lead tracking**:

```typescript theme={null}
// Track initial signup (deferred)
await dub.track.lead({
  clickId: cookies.get("dub_id"),
  eventName: "Sign Up",
  customerExternalId: user.id,
  mode: "deferred", // Creates customer link but defers reward
});

// Later, when qualified
await dub.track.lead({
  clickId: "", // Empty - uses existing customer record
  eventName: "Qualified Lead",
  customerExternalId: user.id, // Same customer ID
});
```

This ensures partners are only rewarded when customers reach a meaningful milestone.

Learn more about [deferred lead tracking](/docs/conversions/leads/deferred).

## Troubleshooting attribution

### Common issues

| Issue                      | Cause                                 | Solution                                            |
| -------------------------- | ------------------------------------- | --------------------------------------------------- |
| Conversions not attributed | `dub_id` cookie missing               | Verify SDK installation and allowed hostnames       |
| Wrong partner credited     | Last-click model with multiple clicks | Consider first-click model if appropriate           |
| Duplicate leads            | Same customer tracked twice           | Ensure consistent `customerExternalId`              |
| Missing commission         | Sale tracked before lead              | Track lead event first, or use direct sale tracking |

### Verifying attribution

1. **Check the cookie**: Inspect browser cookies for `dub_id`
2. **Test the flow**: Click a partner link, sign up, and verify the lead appears in your dashboard
3. **Review analytics**: Check the Events tab for click → lead → sale progression


# Deep link attribution
Source: https://dub.co/docs/concepts/deep-links/attribution

Learn how to use deep link attribution to track conversions events with Dub.

<Note>
  Deep link attribution requires a [Business
  plan](https://dub.co/pricing/partners) subscription or higher.
</Note>

Dub's powerful [attribution platform](/docs/concepts/attribution) lets you understand how well your deep links are translating to actual users and revenue dollars inside your app.

<Frame>
  <img alt="Conversion analytics" />
</Frame>

<Note>
  This feature is currently only available for iOS (Swift) and React Native.
  Android (Kotlin) support is coming soon. If you'd like early access, please
  [contact us](https://dub.co/contact/support).
</Note>

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'll need generate a [publishable key](/docs/api-reference/authentication#publishable-keys) from your Dub workspace to track conversions on the client-side.

To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

<Frame>
  <img alt="Enabling conversion tracking for a workspace" />
</Frame>

Once these are set up, we can start tracking conversion events for your deep links.

## Step 1: Install the client-side Mobile SDK

<Tabs>
  <Tab title="React Native">
    Install the [Dub React Native SDK](/docs/sdks/client-side-mobile/installation-guides/react-native) and initialize it with your publishable key and short link domain.

    <Steps>
      <Step title="Install the Dub React Native SDK">
        ```sh theme={null}
        # With npm
        npm install @dub/react-native

        # With yarn
        yarn add @dub/react-native

        # With pnpm
        pnpm add @dub/react-native
        ```
      </Step>

      <Step title="Initialize the SDK">
        You must call `init` on your `dub` instance with your publishable key and domain prior to being able to use the `dub` instance. We provide two ways to initialize the SDK:

        **Option 1**: Use the `DubProvider` to wrap your app

        ```typescript theme={null}
        import { DubProvider } from "@dub/react-native";

        export default function App() {
          return (
            <DubProvider
              publishableKey="<DUB_PUBLISHABLE_KEY>"
              dubDomain="<DUB_DOMAIN>"
            >
              // Your app content...
            </DubProvider>
          );
        }
        ```

        **Option 2**: Manually initialize the Dub SDK

        ```typescript theme={null}
        import dub from "@dub/react-native";

        export default function App() {
          useEffect(() => {
            dub.init({
              publishableKey: "<DUB_PUBLISHABLE_KEY>",
              domain: "<DUB_DOMAIN>",
            });
          }, []);

          // Return your app...
        }
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="iOS">
    Install the [Dub iOS SDK](/docs/sdks/client-side-mobile/installation-guides/swift) and initialize it with your publishable key and short link domain.

    <Steps>
      <Step title="Install the Dub iOS SDK">
        Before installing, ensure your environment meets these minimum requirements:

        **Build Tools:**

        * Xcode 16+
        * Swift 4.0+

        **Platforms:**

        * iOS 16.0+
        * macOS 10.13 (Ventura)+

        The Dub iOS SDK can be installed using the [Swift Package Manager](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/).

        In Xcode, select **File** > **Add Package Dependencies** and add `https://github.com/dubinc/dub-ios` as the repository URL. Select the latest version of the SDK from the [release page](https://github.com/dubinc/dub-ios/releases).
      </Step>

      <Step title="Initialize the SDK">
        You must call `Dub.setup` with your publishable key and domain prior to being able to use the `dub` instance.

        ```swift iOS (SwiftUI) theme={null}
        import SwiftUI
        import Dub

        @main
        struct DubApp: App {
          // Step 1: Obtain your Dub domain and publishable key
          private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
          private let dubDomain = "<DUB_DOMAIN>"

          init() {
            // Step 2: Initialize the Dub SDK by calling `setup`
            Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)
          }

          var body: some Scene {
            WindowGroup {
                ContentView()
                    // Step 3: Expose the `dub` instance as a SwiftUI environment value
                    .environment(\.dub, Dub.shared)
            }
          }
        }
        ```

        ```swift iOS (UIKit) theme={null}
        import UIKit
        import Dub

        @main
        class AppDelegate: UIResponder, UIApplicationDelegate {

            var window: UIWindow?

            // Step 1: Obtain your Dub domain and publishable key
            private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
            private let dubDomain = "<DUB_DOMAIN>"

            func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
                // Step 2: Initialize the Dub SDK by calling `setup`
                Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)
                return true
            }
        }
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Step 2: Track deep link open events

Once the SDK has been initialized, you can start tracking deep link and deferred deep link events.

Call `trackOpen` on the `dub` instance to track deep link and deferred deep link open events. The `trackOpen` function should be called once without a `deepLink` parameter on first launch, and then again with the `deepLink` parameter whenever the app is opened from a deep link.

<CodeGroup>
  ```typescript React Native expandable theme={null}
  import { useState, useEffect, useRef } from "react";
  import { Linking } from "react-native";
  import AsyncStorage from "@react-native-async-storage/async-storage";
  import dub from "@dub/react-native";

  export default function App() {
    useEffect(() => {
      dub.init({
        publishableKey: "<DUB_PUBLISHABLE_KEY>",
        domain: "<DUB_DOMAIN>",
      });

      // Check if this is first launch
      const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

      if (isFirstLaunch === null) {
        await handleFirstLaunch();
        await AsyncStorage.setItem("is_first_launch", "false");
      } else {
        // Handle initial deep link url (Android only)
        const url = await Linking.getInitialURL();

        if (url) {
          await handleDeepLink(url);
        }
      }

      const linkingListener = Linking.addEventListener("url", (event) => {
        handleDeepLink(event.url);
      });

      return () => {
        linkingListener.remove();
      };
    }, []);

    const handleFirstLaunch = async (
      deepLinkUrl?: string | null | undefined,
    ): Promise<void> => {
      try {
        const response = await dub.trackOpen(deepLinkUrl);

        const destinationURL = response.link?.url;
        // Navigate to the destination URL
      } catch (error) {
        // Handle error
      }
    };

    // Return your app...
  }
  ```

  ```swift iOS (SwiftUI) expandable theme={null}
  // ContentView.swift
  import SwiftUI
  import Dub

  struct ContentView: View {

      @Environment(\.dub) var dub: Dub

      @AppStorage("is_first_launch") private var isFirstLaunch = true

      var body: some View {
          NavigationStack {
              VStack {
                  // Your app content
              }
              .onOpenURL { url in
                  trackOpen(deepLink: url)
              }
              .onAppear {
                  if isFirstLaunch {
                      trackOpen()
                      isFirstLaunch = false
                  }
              }
          }
      }

      private func trackOpen(deepLink: URL? = nil) {
          Task {
              do {
                  let response = try await dub.trackOpen(deepLink: deepLink)

                  // Obtain the destination URL from the response
                  guard let url = response.link?.url else {
                      return
                  }

                  // Navigate to the destination URL
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```

  ```swift iOS (UIKit) expandable theme={null}
  import UIKit
  import Dub

  @main
  class AppDelegate: UIResponder, UIApplicationDelegate {

      var window: UIWindow?

      private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
      private let dubDomain = "<DUB_DOMAIN>"

      private var isFirstLaunch: Bool {
          get {
              UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
          }
          set {
              UserDefaults.standard.set(newValue, forKey: "is_first_launch")
          }
      }

      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
          Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

          // Track first launch
          if isFirstLaunch {
              trackOpen()
              isFirstLaunch = false
          }

          // Override point for customization after application launch.
          return true
      }

      func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
          handleDeepLink(url: url)
          return true
      }

      func handleDeepLink(url: URL) {
          trackOpen(deepLink: url)
      }

      private func trackOpen(deepLink: URL? = nil) {
          // Call the tracking endpoint with the full deep link URL
          Task {
              do {
                  let response = try await Dub.shared.trackOpen(deepLink: deepLink)

                  print(response)

                  // Navigate to final link via link.url
                  guard let destinationUrl = response.link?.url else {
                      return
                  }

                  // Navigate to the destination URL
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```
</CodeGroup>

If the deep link was successfully resolved and correlated to the original click, the `response` object will contain the destination URL, which you can use to navigate the user to the appropriate screen.

It will also contain the `clickId`, which the `dub` instance will persist internally.

## Step 3: Track conversion events

You may track conversion events directly in your app with the `trackLead` and `trackSale` methods.

<CodeGroup>
  ```typescript React Native expandable theme={null}
  import dub from "@dub/react-native";

  function trackLead(user: User) {
    try {
      await dub.trackLead({
        eventName: "User Sign Up",
        customerExternalId: user.id,
        customerName: user.name,
        customerEmail: user.email,
      });
    } catch (error) {
      // Handle sale tracking error
    }
  }

  function trackSale(user: User, product: Product) {
    try {
      await dub.trackSale({
        customerExternalId: user.id,
        amount: product.price.amount,
        currency: "usd",
        eventName: "Purchase",
      });
    } catch (error) {
      // Handle sale tracking error
    }
  }
  ```

  ```swift iOS (SwiftUI) expandable theme={null}
  // ContentView.swift
  import SwiftUI
  import Dub

  struct ContentView: View {

      @Environment(\.dub) var dub: Dub

      var body: some View {
          // ... your app content ...
      }

      private func trackLead(customerExternalId: String, name: String, email: String) {
          Task {
              do {
                  let response = try await dub.trackLead(
                      eventName: "Sign Up",
                      customerExternalId: customerExternalId,
                      customerName: name,
                      customerEmail: email
                  )

                  print(response)
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }

      private func trackSale(
          customerExternalId: String,
          amount: Int,
          currency: String = "usd",
          eventName: String? = "Purchase",
          customerName: String? = nil,
          customerEmail: String? = nil,
          customerAvatar: String? = nil
      ) {
          Task {
              do {
                  let response = try await dub.trackSale(
                      customerExternalId: customerExternalId,
                      amount: amount,
                      currency: currency,
                      eventName: eventName,
                      customerName: customerName,
                      customerEmail: customerEmail,
                      customerAvatar: customerAvatar
                  )

                  print(response)
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```

  ```swift iOS (UIKit) expandable theme={null}
  // ViewController.swift
  import UIKit
  import Dub

  class ViewController: UIViewController {
      // View controller lifecycle

      private func trackLead(customerExternalId: String, name: String, email: String) {
          Task {
              do {
                  let response = try await dub.trackLead(customerExternalId: customerExternalId, name: name, email: email)
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }

      private func trackSale(customerExternalId: String, amount: Int, currency: String = "usd", eventName: String? = "Purchase", customerName: String? = nil, customerEmail: String? = nil, customerAvatar: String? = nil) {
          Task {
              do {
                  let response = try await dub.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```
</CodeGroup>

Alternatively, you can [track conversion events server-side](/docs/quickstart/server) by sending the `clickId` resolved from the deep link to your backend and then calling off to either:

* [`POST /track/lead`](/docs/api-reference/track/lead)
* [`POST /track/sale`](/docs/api-reference/track/sale)

## Step 4: View your conversions

Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://app.dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Deferred deep linking
Source: https://dub.co/docs/concepts/deep-links/deferred-deep-linking

Learn how to use deferred deep linking to track conversions and traffic.

<Note>
  Deep links require a [Pro plan](https://dub.co/pricing) subscription or
  higher.
</Note>

Deferred deep linking allows you to track which link a user came from even when they don't have your app installed.

<Frame>
  <img alt="Deferred deep linking on Dub" />
</Frame>

When a user clicks a link without the app installed, they're redirected to the app store. After installing and opening the app, you can retrieve the original link information and redirect them to the appropriate screen.

## Android Play Store

Android provides the [Install Referrer API](https://developer.android.com/google/play/installreferrer) which allows you to retrieve information about how a user came to install your app, including the referrer URL.

Here is how it works in a nutshell:

1. User taps a deep link on a device without your app installed
2. Dub redirects the user to the App Store or Play Store by using [device targeting](/help/article/device-targeting)
3. User installs your app from the app store
4. App reads the install referrer on first launch
5. App extracts the deep link from the referrer URL and tracks the deep link open event using the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open)
6. Redirect the user to the appropriate screen using the destination URL returned by the `/track/open` endpoint

### Understanding the Referrer URL Structure

When Dub redirects users to the Play Store, the referrer URL contains the deep link information in a nested structure. The referrer URL looks like this:

```
https://play.google.com/store/apps/details?id=com.example.app&referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps
```

To extract the original deep link, you need to:

1. Parse the `referrer` parameter from the Play Store URL
2. URL decode it to get the `deepLink` parameter
3. URL decode the `deepLink` value to get the actual deep link

The code examples below show how to implement this extraction process.

### Step 1: Add the Install Referrer dependency

First, you'll need to add the Google Play Install Referrer library to your project.

<CodeGroup>
  ```javascript React Native theme={null}
  // package.json
  {
    "dependencies": {
      "react-native-play-install-referrer": "latest"
    }
  }
  ```

  ```kotlin Android theme={null}
  // app/build.gradle
  dependencies {
      implementation 'com.android.installreferrer:installreferrer:2.2'
  }
  ```
</CodeGroup>

### Step 2: Implement the Install Referrer logic

Now you'll need to implement the logic to read the install referrer, extract the deep link, and track the deep link open.

<CodeGroup>
  ```javascript React Native expandable theme={null}
  // InstallReferrerTracker.js
  import { PlayInstallReferrer } from "react-native-play-install-referrer";

  class InstallReferrerTracker {
    constructor() {
      this.isFirstLaunch = true;
    }

    trackInstallReferrer() {
      // Check if this is the first launch
      if (!this.isFirstLaunch) {
        return;
      }

      PlayInstallReferrer.getInstallReferrerInfo((installReferrerInfo, error) => {
        if (!error) {
          console.log(
            "Install referrer = " + installReferrerInfo.installReferrer,
          );

          if (installReferrerInfo.installReferrer) {
            // Extract the deep link from the referrer URL
            const deepLink = this.extractDeepLinkFromReferrer(
              installReferrerInfo.installReferrer,
            );
            if (deepLink) {
              // Track the deep link open with the extracted URL
              this.trackDeepLinkOpen(deepLink);
            }
          }
        } else {
          console.log("Failed to get install referrer info!");
          console.log("Response code: " + error.responseCode);
          console.log("Message: " + error.message);
        }

        this.isFirstLaunch = false;
      });
    }

    extractDeepLinkFromReferrer(referrerUrl) {
      try {
        // Parse the referrer URL to extract the deep link
        // e.g. for referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps
        // the deep link is https://dub.sh/gps
        const referrerUrlObj = new URL(referrerUrl);
        const deepLinkParam = referrerUrlObj.searchParams.get("deepLink");
        return decodeURIComponent(deepLinkParam);
        return null;
      } catch (error) {
        console.error("Error extracting deep link from referrer:", error);
        return null;
      }
    }

    async trackDeepLinkOpen(deepLink) {
      try {
        const response = await fetch("https://api.dub.co/track/open", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            deepLink,
          }),
        });

        if (response.ok) {
          const data = await response.json();
          const destinationUrl = data.link.url;

          // Navigate to the destination URL in your app
          this.navigateToDestination(destinationUrl);
        }
      } catch (error) {
        console.error("Error tracking deep link open:", error);
      }
    }

    navigateToDestination(destinationUrl) {
      // Implement your navigation logic here
      // This will depend on your navigation library (React Navigation, etc.)
      console.log("Navigating to:", destinationUrl);
    }
  }

  export default InstallReferrerTracker;
  ```

  ```kotlin Android expandable theme={null}
  // InstallReferrerTracker.kt
  import android.content.Context
  import com.android.installreferrer.api.InstallReferrerClient
  import com.android.installreferrer.api.InstallReferrerResponse
  import com.android.installreferrer.api.ReferrerDetails
  import com.android.installreferrer.api.InstallReferrerStateListener
  import org.json.JSONObject
  import java.net.HttpURLConnection
  import java.net.URL

  class InstallReferrerTracker(private val context: Context) {
      private var isFirstLaunch = true

      fun trackInstallReferrer() {
          if (!isFirstLaunch) {
              return
          }

          val referrerClient = InstallReferrerClient.newBuilder(context).build()

          referrerClient.startConnection(object : InstallReferrerStateListener {
              override fun onInstallReferrerSetupFinished(responseCode: Int) {
                  when (responseCode) {
                      InstallReferrerResponse.OK -> {
                          val response: ReferrerDetails = referrerClient.installReferrer
                          val referrerUrl = response.installReferrer

                          if (!referrerUrl.isNullOrEmpty()) {
                              // Extract the deep link from the referrer URL
                              val deepLink = extractDeepLinkFromReferrer(referrerUrl)
                              if (!deepLink.isNullOrEmpty()) {
                                  // Track the deep link open with the extracted URL
                                  trackDeepLinkOpen(deepLink)
                              }
                          }
                      }
                      InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
                          // API not available on the current Play Store app
                          println("Install Referrer API not supported")
                      }
                      InstallReferrerResponse.SERVICE_UNAVAILABLE -> {
                          // Connection couldn't be established
                          println("Install Referrer service unavailable")
                      }
                  }
                  referrerClient.endConnection()
              }

              override fun onInstallReferrerServiceDisconnected() {
                  // Try to restart the connection on the next request
              }
          })
      }

          private fun extractDeepLinkFromReferrer(referrerUrl: String): String? {
          return try {
              // e.g. for referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps
              // the deep link is https://dub.sh/gps
              val referrerUrlObj = java.net.URL(referrerUrl)
              val query = referrerUrlObj.query ?: return null

              // Parse query parameters
              val params = query.split("&").associate { param ->
                  val keyValue = param.split("=", limit = 2)
                  if (keyValue.size == 2) {
                      keyValue[0] to keyValue[1]
                  } else {
                      keyValue[0] to ""
                  }
              }

              val deepLinkParam = params["deepLink"] ?: return null

              // URL decode the deepLinkParam value to get the actual deep link
              java.net.URLDecoder.decode(deepLinkParam, "UTF-8")
          } catch (e: Exception) {
              println("Error extracting deep link from referrer: ${e.message}")
              null
          }
      }

      private fun trackDeepLinkOpen(deepLink: String) {
          Thread {
              try {
                  val url = URL("https://api.dub.co/track/open")
                  val connection = url.openConnection() as HttpURLConnection

                  connection.requestMethod = "POST"
                  connection.setRequestProperty("Content-Type", "application/json")
                  connection.doOutput = true

                  val body = JSONObject().apply {
                      put("deepLink", deepLink)
                  }

                  connection.outputStream.use { os ->
                      os.write(body.toString().toByteArray())
                  }

                  val responseCode = connection.responseCode
                  if (responseCode == HttpURLConnection.HTTP_OK) {
                      val response = connection.inputStream.bufferedReader().use { it.readText() }
                      val jsonResponse = JSONObject(response)
                      val link = jsonResponse.getJSONObject("link")
                      val destinationUrl = link.getString("url")

                      // Navigate to the destination URL on the main thread
                      navigateToDestination(destinationUrl)
                  }
              } catch (e: Exception) {
                  println("Error tracking deep link open: ${e.message}")
              }
          }.start()
      }

      private fun navigateToDestination(destinationUrl: String) {
          // Implement your navigation logic here
          // This will depend on your navigation library (Navigation Component, etc.)
          println("Navigating to: $destinationUrl")
      }
  }
  ```
</CodeGroup>

### Step 3: Initialize the tracker in your app

Now you need to initialize the Install Referrer tracker when your app starts.

<CodeGroup>
  ```javascript React Native theme={null}
  // App.js
  import React, { useEffect } from 'react';
  import InstallReferrerTracker from './InstallReferrerTracker';

  const App = () => {
    useEffect(() => {
      const tracker = new InstallReferrerTracker();

      // Track install referrer on app launch
      tracker.trackInstallReferrer();
    }, []);

    return (
      // Your app components
    );
  };

  export default App;
  ```

  ```kotlin Android theme={null}
  // MainActivity.kt
  import android.os.Bundle
  import androidx.appcompat.app.AppCompatActivity

  class MainActivity : AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)

          // Initialize and track install referrer
          val tracker = InstallReferrerTracker(this)
          tracker.trackInstallReferrer()
      }
  }
  ```
</CodeGroup>

### Step 4: Handle the navigation

Finally, implement the navigation logic to redirect users to the appropriate screen based on the destination URL.

<CodeGroup>
  ```javascript React Native expandable theme={null}
  // InstallReferrerTracker.js (navigation method)
  navigateToDestination(destinationUrl) {
    // Parse the destination URL to determine which screen to navigate to
    const url = new URL(destinationUrl);
    const path = url.pathname;

    // Example navigation logic
    if (path.includes('/product/')) {
      const productId = path.split('/product/')[1];
      // Navigate to product detail screen
      navigation.navigate('ProductDetail', { productId });
    } else if (path.includes('/category/')) {
      const categoryId = path.split('/category/')[1];
      // Navigate to category screen
      navigation.navigate('Category', { categoryId });
    } else {
      // Navigate to home screen
      navigation.navigate('Home');
    }
  }
  ```

  ```kotlin Android expandable theme={null}
  // InstallReferrerTracker.kt (navigation method)
  import android.content.Intent
  import android.net.Uri
  import android.os.Handler
  import android.os.Looper

  private fun navigateToDestination(destinationUrl: String) {
      Handler(Looper.getMainLooper()).post {
          try {
              val uri = Uri.parse(destinationUrl)
              val path = uri.path ?: ""

              // Example navigation logic
              when {
                  path.contains("/product/") -> {
                      val productId = path.split("/product/").lastOrNull()
                      // Navigate to product detail screen
                      val intent = Intent(context, ProductDetailActivity::class.java).apply {
                          putExtra("productId", productId)
                      }
                      context.startActivity(intent)
                  }
                  path.contains("/category/") -> {
                      val categoryId = path.split("/category/").lastOrNull()
                      // Navigate to category screen
                      val intent = Intent(context, CategoryActivity::class.java).apply {
                          putExtra("categoryId", categoryId)
                      }
                      context.startActivity(intent)
                  }
                  else -> {
                      // Navigate to home screen
                      val intent = Intent(context, HomeActivity::class.java)
                      context.startActivity(intent)
                  }
              }
          } catch (e: Exception) {
              println("Error navigating to destination: ${e.message}")
          }
      }
  }
  ```
</CodeGroup>

### Important notes

1. **First Launch Detection**: The install referrer is only available on the first launch after installation. Make sure to track this properly to avoid duplicate tracking.
2. **URL Decoding**: The referrer URL is URL-encoded multiple times. Make sure to properly decode it to extract the original deep link.
3. **Network Operations**: Ensure that all API calls are made in background threads to avoid blocking the main thread and ensure smooth app performance.
4. **Error Handling**: Always implement proper error handling for network requests and URL parsing.
5. **Testing**: Test your implementation thoroughly using the Google Play Console's internal testing track.

To get started, we recommend using the [quickstart guide](/docs/concepts/deep-links/quickstart) to set up your deep links on Dub.

### Related resources

* [Google Play Install Referrer API Documentation](https://developer.android.com/google/play/installreferrer)
* [Android Deep Linking Guide](https://developer.android.com/training/app-links)
* [Dub API Documentation](/docs/api-reference/introduction)

## iOS App Store

Unlike Android, iOS doesn't provide a built-in install referrer API. Dub implements a hybrid approach combining **deterministic tracking** (clipboard-based) and **probabilistic tracking** (IP-based) to ensure reliable deferred deep linking on iOS.

### How iOS deferred deep linking works on Dub

Dub's iOS deferred deep linking solution provides two tracking approaches:

1. **Deterministic clipboard-based tracking**: Uses iOS clipboard data to reliably identify and open the exact deep link the user interacted with.
2. **Probabilistic IP-based tracking**: Matches users from the web to the app based on their IP address.

For the deterministic approach, when an iOS user who doesn't have your app installed clicks on your deep link, Dub redirects them to a custom landing page:

<Frame>
  <img alt="iOS deferred deep linking landing page" />
</Frame>

This page displays two options:

* **Get the App**: Copies the deep link to the clipboard before redirecting to the App Store.
* **Get the App without Copying**: Redirects directly to the App Store without copying the deep link to the clipboard.

After the user installs your app, the deep link resolution method depends on the button they clicked.

#### 1. Deterministic clipboard-based tracking

This method uses the iOS clipboard to pass the deep link into your app after installation, ensuring reliable and direct link resolution.

Here is how it works in a nutshell:

1. User taps **Get the App** button on the landing page.
2. Dub copies the deep link URL to the clipboard.
3. The user is redirected to the App Store to download your app.
4. After installation, your app reads the clipboard to retrieve the deep link.
5. Your app calls the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open) with the `deepLink` parameter in the request body either directly or via a supported [Dub Mobile SDK](/docs/sdks/client-side-mobile/introduction).
6. Dub returns the destination URL, and your app navigates the user to the appropriate screen (see [deep links quickstart](/docs/concepts/deep-links/quickstart) for more details).

#### 2. Probabilistic IP-based tracking

This method relies on matching the user’s device IP address from the initial click event to the app open event after installation.

1. User taps **Get the App without Copying** button on the landing page.
2. The user is redirected directly to the App Store to download your app.
3. Dub has already tracked the click and stored the IP address at the time of deep link click.
4. After installation, your app calls the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open) with the `dubDomain` parameter in the request body either directly or via a supported [Dub Mobile SDK](/docs/sdks/client-side-mobile/introduction) .
5. Dub matches the user using IP-based tracking and returns the destination URL.
6. If a destination URL is returned, your app navigates the user to the appropriate screen (see [deep links quickstart](/docs/concepts/deep-links/quickstart) for more details).

### Track the deep link open

The following logic determines whether to use clipboard-based tracking or IP-based tracking:

* **Clipboard-based tracking**: Used when a deep link is found in the clipboard (e.g., `acme.link`). The app sends the `deepLink` parameter in the request body.
* **IP-based tracking**: Used when no deep link is found in the clipboard or the user declined paste permissions. The app sends only the `dubDomain` parameter, allowing Dub to match the user based on their IP address.

This ensures the most reliable tracking method is chosen automatically.

<CodeGroup>
  ```javascript React Native expandable theme={null}
  // App.js
  import React, { useEffect } from 'react';
  import Clipboard from '@react-native-clipboard/clipboard';
  import AsyncStorage from '@react-native-async-storage/async-storage';

  export default function App() {
    useEffect(() => {
      trackOpen();
    }, []);

    return (
      // Your app components
    );
  }

  // Make request to /track/open endpoint
  async function trackOpen() {
    try {
      // Check if this is first launch
      const hasLaunched = await AsyncStorage.getItem('app_first_launch');
      if (hasLaunched !== null) {
        return;
      }
      await AsyncStorage.setItem('app_first_launch', 'false');

      // Check clipboard for deep link
      const clipboard = await Clipboard.getString();
      let requestBody;

      if (clipboard && clipboard.includes('acme.link')) {
        // Clipboard-based tracking
        requestBody = { deepLink: clipboard };
      } else {
        // IP-based tracking fallback
        requestBody = { dubDomain: 'acme.link' };
      }

      const response = await fetch('https://api.dub.co/track/open', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(requestBody),
      });

      if (response.ok) {
        const data = await response.json();
        const destinationURL = data.link?.url;

        if (destinationURL) {
          // Navigate to the destination URL
          console.log('Navigating to:', destinationURL);
        }
      }
    } catch (error) {
      console.error('Error tracking open:', error);
    }
  }
  ```

  ```swift iOS (SwiftUI) expandable theme={null}
  import SwiftUI

  @main
  struct MyApp: App {
    var body: some Scene {
      WindowGroup {
        ContentView().onAppear {
          trackOpen()
        }
      }
    }
  }

  // Make request to /track/open endpoint
  func trackOpen(completion: @escaping (String?) -> Void) {
    let clipboard = UIPasteboard.general.string
    var requestBody: [String: String]

    if let deepLink = clipboard, deepLink.contains("acme.link") {
      // Clipboard-based tracking
      requestBody = ["deepLink": deepLink]
    } else {
      // IP-based tracking fallback
      requestBody = ["dubDomain": "acme.link"]
    }

    guard let url = URL(string: "https://api.dub.co/track/open") else {
      completion(nil)
      return
    }

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    do {
      request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
    } catch {
      print("Error creating request body:", error)
      completion(nil)
      return
    }

    URLSession.shared.dataTask(with: request) { data, response, error in
      if let error = error {
        print("Error tracking open:", error)
        completion(nil)
        return
      }

      guard let data = data else {
        print("No data received")
        completion(nil)
        return
      }

      do {
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        let link = json?["link"] as? [String: Any]
        let destinationURL = link?["url"] as? String
        completion(destinationURL)
      } catch {
        print("Error parsing response:", error)
        completion(nil)
      }
    }.resume()
  }

  ```
</CodeGroup>

### Handle the navigation

Once you have the deep link URL from your `trackOpen` function, you can route the user to the appropriate screen.

<CodeGroup>
  ```javascript React Native expandable theme={null}
  // App.js with React Navigation
  import React, { useEffect, useRef } from "react";
  import { NavigationContainer } from "@react-navigation/native";
  import { createNativeStackNavigator } from "@react-navigation/native-stack";

  const Stack = createNativeStackNavigator();

  export default function App() {
    const navigationRef = useRef();

    useEffect(() => {
      const handleDeepLink = async () => {
        const destinationURL = await trackOpen();

        if (destinationURL && navigationRef.current) {
          // Parse URL and navigate
          const url = new URL(destinationURL);
          const path = url.pathname;

          if (path.includes("/product/")) {
            const productId = path.split("/product/")[1];
            navigationRef.current.navigate("Product", { productId });
          } else {
            navigationRef.current.navigate("Home");
          }
        }
      };

      handleDeepLink();
    }, []);

    return (
      <NavigationContainer ref={navigationRef}>
        <Stack.Navigator initialRouteName="Home">
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Product" component={ProductScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    );
  }
  ```

  ```swift iOS (SwiftUI) expandable theme={null}
  import SwiftUI

  struct ContentView: View {
    @State private var deepLinkURL: String?
    @State private var shouldNavigate = false

    var body: some View {
      NavigationStack {
        VStack {
          Text("Home Screen")
        }
        .onAppear {
          trackOpen { url in
            if let url = url {
              DispatchQueue.main.async {
                deepLinkURL = url
                shouldNavigate = true
              }
            }
          }
        }
        .navigationDestination(isPresented: $shouldNavigate) {
          if let url = deepLinkURL {
            DetailView(url: url)
          }
        }
      }
    }
  }

  struct DetailView: View {
    let url: String

    var body: some View {
      Text("Navigated to: \(url)")
    }
  }
  ```
</CodeGroup>

### Important notes

1. **IP-based Tracking Limitations**: IP-based tracking relies on the device’s network IP, which can be less reliable in environments like corporate networks, VPNs, or shared Wi-Fi.
2. **Network Operations**: Ensure that all API calls are made in background threads to avoid blocking the main thread and ensure smooth app performance.
3. **Error Handling**: Implement proper error handling for network requests, clipboard access to ensure the app behaves gracefully under failures.
4. **Testing**: Test thoroughly using TestFlight or an internal distribution build to confirm the flow works for both clipboard-based and IP-based tracking scenarios.
5. **App Store Guidelines**: Ensure your app complies with Apple's App Store guidelines regarding clipboard access and user privacy.

### Related resources

* [iOS Deep Linking Guide](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content)
* [`/track/open` API endpoint reference](/docs/api-reference/endpoint/track-open)
* [Dub Deep Links Quickstart](/docs/concepts/deep-links/quickstart)


# Migrating from Firebase Dynamic Links
Source: https://dub.co/docs/concepts/deep-links/migrating-from-firebase

Learn how to migrate from Firebase Dynamic Links to Dub.

<Note>
  Deep links require a [Pro plan](https://dub.co/pricing) subscription or
  higher.
</Note>

This guide walks you through replacing [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links) with Dub for deep link creation, click tracking, and routing across platforms.

## Prerequisites

Before you begin, make sure to follow the [quickstart guide](/docs/concepts/deep-links/quickstart) to set up deep links on Dub by completing the following steps:

1. Configure your [deep link domain](/docs/concepts/deep-links/quickstart#step-1-configure-your-deep-link-domains-on-dub) on Dub. This replaces Firebase's `*.page.link` domain with your own branded deep link domain.
2. [Create your deep links](/docs/concepts/deep-links/quickstart#step-2%3A-create-your-deep-links-on-dub) on Dub.
3. [Handle deep link redirects inside your app](/docs/concepts/deep-links/quickstart#step-3%3A-handling-deep-link-redirects-in-your-app) (more details below).

## Migrating Android apps

<Steps>
  <Step title="Remove Firebase Dynamic Links SDK">
    If you have the Firebase Dynamic Links SDK installed, remove it from your `build.gradle` file:

    ```gradle theme={null}
    implementation 'com.google.firebase:firebase-dynamic-links:21.1.0'
    ```

    Remove any Firebase imports from your Kotlin/Java files:

    ```java theme={null}
    import com.google.firebase.dynamiclinks.FirebaseDynamicLinks
    import com.google.firebase.dynamiclinks.DynamicLink
    ```
  </Step>

  <Step title="Update AndroidManifest.xml">
    If you’re using the same domain for both Firebase Dynamic Links and Dub (e.g., yourapp.com), no changes are needed — you can skip this section.

    However, if you were previously using Firebase’s branded domain (e.g., yourapp.page.link), you’ll need to replace it with your custom Dub domain in your app’s configuration.

    ```xml theme={null}
    <activity android:name=".MainActivity">
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
          android:scheme="https"
          android:host="yourapp.page.link" /> <!-- Replace with your Dub deep link domain -->
      </intent-filter>
    </activity>
    ```
  </Step>

  <Step title="Implement Deep Link Handling">
    Override `onNewIntent` in your main activity to handle deep link opens:

    ```kotlin theme={null}
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.data?.let { uri ->
            handleDeepLink(uri)
        }
    }

    private fun handleDeepLink(uri: Uri) {
        // Track the deep link open and get destination URL
        trackDeepLinkClick(uri.toString())
    }

    private fun trackDeepLinkClick(deepLink: String) {
        val url = URL("https://api.dub.co/track/open")
        val connection = url.openConnection() as HttpURLConnection

        connection.requestMethod = "POST"
        connection.setRequestProperty("Content-Type", "application/json")
        connection.doOutput = true

        val body = JSONObject().apply {
            put("deepLink", deepLink)
        }

        connection.outputStream.use { os ->
            os.write(body.toString().toByteArray())
        }

        val response = connection.inputStream.bufferedReader().use { it.readText() }
        val jsonResponse = JSONObject(response)
        val destinationUrl = jsonResponse.getString("url")

        runOnUiThread {
            navigateToDestination(destinationUrl)
        }
    }
    ```

    In a nutshell, this code:

    * Extracts the domain and key from the deep link URL
    * Sends the data to the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open)
    * Retrieves the final destination URL
    * Navigates to the destination URL in the app
  </Step>
</Steps>

## Migrating iOS apps

<Steps>
  <Step title="Remove Firebase Dynamic Links SDK">
    If you have the Firebase Dynamic Links SDK installed, remove it from your `Podfile`:

    ```ruby theme={null}
    pod 'Firebase/DynamicLinks'
    ```

    Remove any Firebase imports like:

    ```swift theme={null}
    import FirebaseDynamicLinks
    ```
  </Step>

  <Step title="Update Associated Domains">
    If you're using the same domain for both Firebase Dynamic Links and Dub (e.g., yourapp.com), no changes are needed — you can skip this section.

    However, if you were using Firebase’s branded domain (e.g., yourapp.page.link), you’ll need to replace it with your Dub domain in your iOS project setup.

    To enable Universal Links with Dub, update your Associated Domains in Xcode:

    1. Open your Xcode project.
    2. Select your app target → **Signing & Capabilities**
    3. Under **Associated Domains**, replace the old Firebase domain with your Dub domain:

    <Warning>
      If your app uses other SDKs that handle URLs (e.g., Facebook/Meta SDK, Google Sign-In), make sure your `AppDelegate`'s `openURL` handler doesn't short-circuit with `||`. When a third-party SDK returns `true` first, Dub's `handleDubDeepLink` will never be called. See the [troubleshooting guide](/docs/concepts/deep-links/quickstart#troubleshooting-deep-links) for details and a fix.
    </Warning>

    No changes to your app’s `Info.plist` are required unless you had other Firebase-specific deep link configurations.

    ```swift theme={null}
    import Foundation
    import UIKit

    func handleDubDeepLink(\_ url: URL) {
    let domain = url.host ?? ""
    let key = url.lastPathComponent

    let payload: [String: Any] = [
      "domain": domain,
      "key": key,
    ]

    guard let requestURL = URL(string: "https://api.dub.co/track/open"),
      let httpBody = try? JSONSerialization.data(withJSONObject: payload) else {
        print("Invalid request setup")
        return
    }

    var request = URLRequest(url: requestURL)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = httpBody

    // Send the request
    URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            print("Failed to track deep link: \(error)")
            return
        }

        guard let data = data,
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let destination = json["url"] as? String else {
            print("Invalid response from /track/open")
            return
        }

        print("Resolved destination: \(destination)")

        DispatchQueue.main.async {
            // Navigate to the destination URL or handle it in-app
            navigateToScreen(from: destination)
        }
      }.resume()
    }


    func navigateToScreen(from url: String) {
      print("Navigating to in-app destination: \(url)")
      // Add your navigation logic here
    }
    ```

    In a nutshell, this code:

    * Sends the deep link URL to the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open)
    * Retrieves the final destination URL
    * Navigates to the destination URL in the app
  </Step>
</Steps>


# Quickstart
Source: https://dub.co/docs/concepts/deep-links/quickstart

Learn how to use Dub to create deep links for your mobile app (with native support for React Native, iOS, and Android).

<Note>
  Deep links require a [Pro plan](https://dub.co/pricing) subscription or
  higher.
</Note>

On Dub, you can create deep links that lets you to redirect users to a specific page within your mobile application.

<Frame>
  <img alt="Deep links on Dub" />
</Frame>

For example, you're creating an ad campaign to drive traffic to a specific product page within your mobile app.

By following the quickstart guide below, you'll be able to make sure that all your short links are set up with deep linking functionality.

## Step 1: Configure your deep link domains on Dub

Before you can create deep links, you need to configure your deep link domains in your Dub workspace. This involves adding a custom domain that will be used for your deep links and configuring your deep link configuration files.

### Add a custom domain

First, you'll need to add a custom domain to your Dub workspace. Navigate to your [workspace domain settings](https://app.dub.co/settings/domains) and click **Add Domain**.

You can use a domain you already own, or leverage our [free .link domain offer](/help/article/free-dot-link-domain) to register a custom domain like `yourcompany.link` and use it as your deep link domain.

### Set up your deep link configuration files

Once you've set up your custom domain, you'll need to upload your deep link configuration files to Dub, which we'll host under the `/.well-known/` directory of your domain.

To do that, go to your [workspace domain settings](https://app.dub.co/settings/domains) and click on the edit button for your custom domain:

<Frame>
  <img alt="Deep link configuration files" />
</Frame>

In the domain modal, click on **Show advanced settings**, which will open up the Deep Link Configuration settings fields.

<Frame>
  <img alt="Deep link configuration settings" />
</Frame>

**iOS (apple-app-site-association)**

For iOS apps, upload your [Apple App Site Association file ](https://developer.apple.com/documentation/xcode/supporting-associated-domains#Add-the-associated-domain-file-to-your-website) to enable iOS deep links:

```json apple-app-site-association theme={null}
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "<YOUR_APPLICATION_IDENTIFIER_PREFIX>.<YOUR_APPLICATION_BUNDLE_IDENTIFIER>",
        "paths": []
      }
    ]
  }
}
```

<Note>
  After updating the AASA file, you may need to reinstall your app since iOS can
  cache the old version of the file, which can lead to inconsistent behavior.
</Note>

**Android (assetlinks.json)**

For Android apps, upload your AssetLinks file to enable Android deep links:

```json assetlinks.json theme={null}
[
  {
    "target": {
      "namespace": "android_app",
      "package_name": "YOUR_PACKAGE_NAME",
      "sha256_cert_fingerprints": []
    },
    "relation": ["delegate_permission/common.handle_all_urls"]
  }
]
```

### Verify that your configuration files are set up correctly

Once you've set up your deep link configuration files, you can go to their respective URLs to verify that they've been configured correctly:

**iOS:** `yourdomain.link/.well-known/apple-app-site-association` [(example)](https://getacme.link/.well-known/apple-app-site-association)

**Android:** `yourdomain.link/.well-known/assetlinks.json` [(example)](https://getacme.link/.well-known/assetlinks.json)

### Allowlist your deep link domain in your app

Last but not least, you'll need to allowlist your deep link domain in your apps to allow them to redirect straight into a page within your app.

**For iOS apps**, you'll need to [allow websites to link to your apps](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).

**For Android apps**, you'll need to [verify your Android app links](https://developer.android.com/training/app-links/verify-android-applinks).

## Step 2: Create your deep links on Dub

Now that you've configured your deep link domains, you can create deep links that will redirect users to specific content within your app.

### Enter your destination URL

Go to your [Dub dashboard](https://app.dub.co) and click **Create Link** in the top navigation bar.

Enter your destination URL in the "Destination URL" field. You can enter the URL with or without the `https://` protocol – behind the scenes, Dub will automatically make sure it's formatted correctly.

This is the URL that opens a specific screen or piece of content within your app. For example `https://yourapp.com/product/Apple-MacBook` opens the product detail screen for `Apple-MacBook`.

<Frame>
  <img alt="Deep link builder" />
</Frame>

### Set a short link slug

Under the Short Link field, you can either:

1. Enter your own short link slug
2. Generate a random short link slug
3. Generate a short link slug with AI.

For example, you can set a short link slug like `apple-macbook` to open the product detail screen for `Apple-MacBook`.

<Tip>
  This step is optional. If you don't enter a short link slug, Dub will generate
  a random 7-character slug for you.
</Tip>

### Add device targeting

[Device Targeting](/help/article/device-targeting) enables you to redirect users to the App Store or Google Play Store if your app isn’t installed.

For example, you can set custom destination URLs for different devices using the link builder — use the **iOS Targeting** input for iOS devices, and the **Android Targeting** input for Android devices.

<Frame>
  <img alt="Deep link device targeting configuration" />
</Frame>

Finally, click **Create link** to create your deep link. This link will act as a Firebase Dynamic Link replacement.

<Note>
  Dub's link builder offers many additional features like UTM builder, password
  protection, expiration dates, geo targeting, and more. [Learn more about
  creating links on Dub](/help/article/how-to-create-link) to explore all
  available options.
</Note>

## Step 3: Handling deep link redirects in your app

When a user opens your app from a deep link, you need to handle two main scenarios:

### Scenario 1: User has your app installed

When your app is already installed, the deep link will open your app directly.
You may handle the deep link manually or with the supported mobile SDKs.

**Option 1**: Handle the deep link using a supported [Dub Mobile SDK](/docs/sdks/client-side-mobile/introduction) (iOS & React Native only)

Follow our installation guide for [Swift](/docs/sdks/client-side-mobile/installation-guides/swift) or [React Native](/docs/sdks/client-side-mobile/installation-guides/react-native) to get started.

<CodeGroup>
  ```typescript React Native expandable theme={null}
  import { useState, useEffect, useRef } from "react";
  import { Linking } from "react-native";
  import AsyncStorage from "@react-native-async-storage/async-storage";
  import dub from "@dub/react-native";

  export default function App() {
    useEffect(() => {
      dub.init({
        publishableKey: "<DUB_PUBLISHABLE_KEY>",
        domain: "<DUB_DOMAIN>",
      });

      // Check if this is first launch
      const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

      if (isFirstLaunch === null) {
        await handleFirstLaunch();
        await AsyncStorage.setItem("is_first_launch", "false");
      } else {
        // Handle initial deep link url (Android only)
        const url = await Linking.getInitialURL();

        if (url) {
          await handleDeepLink(url);
        }
      }

      const linkingListener = Linking.addEventListener("url", (event) => {
        handleDeepLink(event.url);
      });

      return () => {
        linkingListener.remove();
      };
    }, []);

    const handleFirstLaunch = async (
      deepLinkUrl?: string | null | undefined,
    ): Promise<void> => {
      try {
        const response = await dub.trackOpen(deepLinkUrl);

        const destinationURL = response.link?.url;
        // Navigate to the destination URL
      } catch (error) {
        // Handle error
      }
    };

    // Return your app...
  }
  ```

  ```swift iOS (SwiftUI) expandable theme={null}
  // ContentView.swift
  import SwiftUI
  import Dub

  struct ContentView: View {

      @Environment(\.dub) var dub: Dub

      @AppStorage("is_first_launch") private var isFirstLaunch = true

      var body: some View {
          NavigationStack {
              VStack {
                  // Your app content
              }
              .onOpenURL { url in
                  trackOpen(deepLink: url)
              }
              .onAppear {
                  if isFirstLaunch {
                      trackOpen()
                      isFirstLaunch = false
                  }
              }
          }
      }

      private func trackOpen(deepLink: URL? = nil) {
          Task {
              do {
                  let response = try await dub.trackOpen(deepLink: deepLink)

                  // Obtain the destination URL from the response
                  guard let url = response.link?.url else {
                      return
                  }

                  // Navigate to the destination URL
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```

  ```swift iOS (UIKit) expandable theme={null}
  import UIKit
  import Dub

  @main
  class AppDelegate: UIResponder, UIApplicationDelegate {

      var window: UIWindow?

      private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
      private let dubDomain = "<DUB_DOMAIN>"

      private var isFirstLaunch: Bool {
          get {
              UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
          }
          set {
              UserDefaults.standard.set(newValue, forKey: "is_first_launch")
          }
      }

      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
          Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

          // Track first launch
          if isFirstLaunch {
              trackOpen()
              isFirstLaunch = false
          }

          // Override point for customization after application launch.
          return true
      }

      func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
          handleDeepLink(url: url)
          return true
      }

      func handleDeepLink(url: URL) {
          trackOpen(deepLink: url)
      }

      private func trackOpen(deepLink: URL? = nil) {
          // Call the tracking endpoint with the full deep link URL
          Task {
              do {
                  let response = try await Dub.shared.trackOpen(deepLink: deepLink)

                  print(response)

                  // Navigate to final link via link.url
                  guard let destinationUrl = response.link?.url else {
                      return
                  }

                  // Navigate to the destination URL
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```
</CodeGroup>

**Option 2**: Handle the deep link manually

<Steps>
  <Step title="Detect the deep link">
    Your app will receive the deep link URL when it opens. The URL will be in the format: `https://yourdomain.link/short-link-slug`

    <CodeGroup>
      ```javascript React Native theme={null}
      // App.js
      import { useEffect } from "react";
      import { Linking } from "react-native";

      useEffect(() => {
        const handleDeepLink = (url) => {
          try {
            // Call the tracking endpoint with the full deep link URL
            trackDeepLinkClick(url);
          } catch (error) {
            console.error("Error handling deep link URL:", error);
          }
        };

        // Handle deep link when app is already running
        const subscription = Linking.addEventListener("url", (event) => {
          handleDeepLink(event.url);
        });

        // Handle deep link when app is opened from a deep link
        Linking.getInitialURL().then((url) => {
          if (url) {
            handleDeepLink(url);
          }
        });

        return () => {
          subscription?.remove();
        };
      }, []);
      ```

      ```swift iOS (UIKit) theme={null}
      // AppDelegate.swift
      func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
          handleDeepLink(url: url)
          return true
      }

      func handleDeepLink(url: URL) {
          // Call the tracking endpoint with the full deep link URL
          trackDeepLinkClick(deepLink: url.absoluteString)
      }
      ```

      ```swift iOS (SwiftUI) theme={null}
      // ContentView.swift
      struct ContentView: View {

          var body: some View {
              NavigationStack {
                  VStack {
                    //... your app content
                  }
                  .onOpenURL { url in
                      // Call the tracking endpoint with the full deep link URL
                      trackDeepLinkClick(deepLink: url.absoluteString)
                  }
              }
          }
      }
      ```

      ```kotlin Android theme={null}
      // MainActivity.kt
      override fun onNewIntent(intent: Intent?) {
          super.onNewIntent(intent)
          intent?.data?.let { uri ->
              handleDeepLink(uri)
          }
      }

      private fun handleDeepLink(uri: Uri) {
          // Call the tracking endpoint with the full deep link URL
          trackDeepLinkClick(uri.toString())
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Track the deep link open">
    Once you’ve detected and parsed the deep link, you should track the open event using Dub's API, which will return the final destination URL in the API response.

    To do this, make a `POST` request to the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open) with the following body:

    ```json theme={null}
    {
      "deepLink": "https://yourdomain.link/short-link-slug"
    }
    ```

    The response will be a JSON object with the following structure if the request is successful:

    ```json theme={null}
    {
      "clickId": "ASltDhoxBiBqKH00",
      "link": {
        "id": "link_1K9XQTLF7OSH3Z48DKQ5WSRVY",
        "domain": "yourdomain.link",
        "key": "short-link-slug",
        "url": "https://yourapp.com/product/Apple-MacBook"
      }
    }
    ```

    Now you've got the destination URL (via `link.url`), you can navigate the user to the correct screen in your app.

    Here's the full example code for React Native, iOS, and Android.

    <CodeGroup>
      ```javascript React Native theme={null}
      // DeepLinkTracker.js
      async function trackDeepLinkClick(deepLink) {
        try {
          const response = await fetch(`https://api.dub.co/track/open`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              deepLink,
            }),
          });

          if (response.ok) {
            const data = await response.json();
            const destinationUrl = data.link.url;

            // Navigate to the destination URL in your app
            navigateToDestination(destinationUrl);
          }
        } catch (error) {
          console.error("Error tracking deep link:", error);
        }
      }
      ```

      ```swift iOS theme={null}
      // DeepLinkTracker.swift
      func trackDeepLinkClick(deepLink: String) {
          let url = URL(string: "https://api.dub.co/track/open")!
          var request = URLRequest(url: url)
          request.httpMethod = "POST"
          request.setValue("application/json", forHTTPHeaderField: "Content-Type")

          let body = [
              "deepLink": deepLink,
          ]

          request.httpBody = try? JSONSerialization.data(withJSONObject: body)

          URLSession.shared.dataTask(with: request) { data, response, error in
              if let data = data,
                 let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
                 let link = json["link"] as? [String: Any],
                 let destinationUrl = link["url"] as? String {
                  DispatchQueue.main.async {
                      self.navigateToDestination(destinationUrl)
                  }
              }
          }.resume()
      }
      ```

      ```kotlin Android theme={null}
      // DeepLinkTracker.kt
      private fun trackDeepLinkClick(deepLink: String) {
          val url = URL("https://api.dub.co/track/open")
          val connection = url.openConnection() as HttpURLConnection

          connection.requestMethod = "POST"
          connection.setRequestProperty("Content-Type", "application/json")
          connection.doOutput = true

          val body = JSONObject().apply {
              put("deepLink", deepLink)
          }

          connection.outputStream.use { os ->
              os.write(body.toString().toByteArray())
          }

          val response = connection.inputStream.bufferedReader().use { it.readText() }
          val jsonResponse = JSONObject(response)
          val link = jsonResponse.getJSONObject("link")
          val destinationUrl = link.getString("url")

          runOnUiThread {
              navigateToDestination(destinationUrl)
          }
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

### Scenario 2: User doesn't have your app installed

When your app isn't installed, the user will be redirected to the App Store or Google Play Store since you've enabled [device targeting](/help/article/device-targeting) in Step 2 above.

After they install and open your app, you'll need to use [deferred deep linking](/docs/concepts/deep-links/deferred-deep-linking) to:

1. Retrieve the short link that brought the user to the app store
2. Track the open event via the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open)
3. Redirect the user to the final destination URL

<Note>
  For detailed implementation of deferred deep linking, including how to use the
  Google Play Store Install Referrer API and other services, see our [Deferred
  Deep Linking](/docs/concepts/deep-links/deferred-deep-linking) guide.
</Note>

## Testing deep links

Before deploying your deep links to production, test them thoroughly using emulators to ensure they work correctly.

<Steps>
  <Step title="Test on Android emulator">
    Use the Android Debug Bridge (adb) to test your deep links on an Android emulator:

    ```bash theme={null}
    adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.link/your-short-link" com.yourpackage.name
    ```
  </Step>

  <Step title="Test on iOS simulator">
    Use the `xcrun` command to test your deep links on an iOS simulator:

    ```bash theme={null}
    xcrun simctl openurl booted "https://yourdomain.link/your-short-link"
    ```
  </Step>

  <Step title="Test on your device">
    You can also test your deep links by opening them from a messaging app on your device:

    1. Open a messaging app (like Messages, WhatsApp, or Telegram) on your device
    2. Send yourself a message containing your deep link URL
    3. Tap on the link in the message
    4. The link should either open your app directly or redirect to the appropriate app store
  </Step>
</Steps>

## Troubleshooting deep links

If your deep links aren't working as expected, here are some common issues and solutions:

<Steps>
  <Step title="Deep link domain not allowlisted">
    Make sure you've [allowlisted your deep link domain](/docs/concepts/deep-links/quickstart#allowlist-your-deep-link-domain-in-your-app) in your app's configuration. This is required for both iOS and Android to recognize and handle links from your domain.
  </Step>

  <Step title="iOS/Android cached outdated deep link configuration">
    If you've completed all steps above and your deep links still aren't working, it's likely because iOS or Android cached the outdated deep link configuration.

    Try uninstalling and reinstalling your app to clear the cache. This forces the operating system to re-fetch the `apple-app-site-association` or `assetlinks.json` files.
  </Step>

  <Step title="Deep link wrapped by a third-party links">
    If your links are being shared through marketing platforms, email providers, or click trackers, the original URL may be getting wrapped or modified.

    When a third-party service wraps your Dub link in their own tracking URL, the deep link won't work because the device sees the wrapper domain instead of your allowlisted domain.

    Contact your marketing or analytics provider to ensure Dub links are passed through without modification.
  </Step>

  <Step title="iOS URL handler short-circuited by third-party SDKs">
    If you're using third-party SDKs (e.g., Facebook/Meta SDK, Google Sign-In, or other authentication SDKs) alongside Dub in an iOS app, your `openURL` handler may silently fail to trigger Dub's deep link tracking.

    This happens when multiple URL handlers are chained with a short-circuit `||` (OR) operator in your `AppDelegate`. If a third-party SDK processes the URL first and returns `true`, subsequent handlers never execute — meaning `RCTLinkingManager` (React Native) or Dub's `handleDeepLink` / `trackOpen` never gets called.

    This is especially common when links are opened from in-app browsers (e.g., Facebook or Instagram's in-app browser).

    **How to fix this:**

    Replace the short-circuit `||` with a non-short-circuit `|` operator (or call all handlers independently) to ensure every SDK gets a chance to process the URL:

    <CodeGroup>
      ```objc AppDelegate.mm (React Native)  theme={null}
      // ❌ Bad: Short-circuit OR — if FBSDKApplicationDelegate returns YES, 
      // RCTLinkingManager never fires
      - (BOOL)application:(UIApplication *)app
                  openURL:(NSURL *)url
                  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
        return [[FBSDKApplicationDelegate sharedInstance] application:app openURL:url options:options]
            || [RCTLinkingManager application:app openURL:url options:options];
      }

      // ✅ Good: Non-short-circuit OR — both handlers always run
      - (BOOL)application:(UIApplication *)app
                  openURL:(NSURL *)url
                  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
        BOOL fbHandled = [[FBSDKApplicationDelegate sharedInstance] application:app openURL:url options:options];
        BOOL rnHandled = [RCTLinkingManager application:app openURL:url options:options];
        return fbHandled || rnHandled;
      }
      ```

      ```swift AppDelegate.swift (Swift) theme={null}
      // ❌ Bad: Short-circuit OR — if the Facebook SDK returns true,
      // handleDeepLink never fires
      func application(_ app: UIApplication, open url: URL,
                       options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
          return ApplicationDelegate.shared.application(app, open: url, options: options)
              || handleDeepLink(url: url)
      }

      // ✅ Good: Call each handler independently
      func application(_ app: UIApplication, open url: URL,
                       options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
          let fbHandled = ApplicationDelegate.shared.application(app, open: url, options: options)
          let dubHandled = handleDeepLink(url: url)
          return fbHandled || dubHandled
      }
      ```
    </CodeGroup>

    <Warning>
      This issue is easy to miss because deep links may still work in most
      scenarios — it only fails when the URL is first processed by another SDK
      (e.g., when a user opens your link from Facebook or Instagram's in-app
      browser). Always test deep links from third-party apps where your SDKs are
      active.
    </Warning>
  </Step>
</Steps>


# Bulk operations
Source: https://dub.co/docs/concepts/links/bulk-operations

Learn how to perform bulk operations on links.

Dub allows you to perform bulk operations on links. This is particularly useful when you need to [create](/docs/api-reference/links/bulk-create), [update](/docs/api-reference/links/bulk-update), or [delete](/docs/api-reference/links/bulk-delete) multiple links at once without having to make multiple API requests.

## Bulk create links

Bulk create allows you to create up to 100 links at once.

<Warning>
  Bulk link creation does not support [custom link
  previews](/help/article/custom-link-previews). Also, [webhook
  events](/docs/webhooks/introduction) will not be triggered when using bulk
  link creation.
</Warning>

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.links.createMany([
    {
      url: "https://google.com",
    },
    {
      url: "https://google.uk",
    },
  ]);
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )
  }

  res, err := s.Links.CreateMany(ctx, []operations.RequestBody{
    operations.RequestBody{
      URL: "https://google.com",
    },
    operations.RequestBody{
      URL: "https://google.uk",
    },
  })
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.links.create_many(request=[
    {
       "url": "https://google.com",
    },
    {
      "url": "https://google.uk",
    },
  ])
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = [
    ::OpenApiSDK::Operations::RequestBody.new(
      url: "https://google.com",
    ),
    ::OpenApiSDK::Operations::RequestBody.new(
      url: "https://example.uk"
    ),
  ]

  res = s.links.create_many(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = [
    new Operations\RequestBody(
      url: 'https://google.com',
    ),
    new Operations\RequestBody(
      url: 'https://google.uk',
    ),
  ];

  $response = $sdk->links->createMany(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.dub.co/links/bulk \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
    --data '[
      {
        "url": "https://google.com"
      },
      {
        "url": "https://google.uk"
      }
    ]'
  ```
</CodeGroup>

Check out the [full API reference for the link bulk creation endpoint](/docs/api-reference/links/bulk-create).

## Bulk update links

Bulk update allows you to modify up to 100 links simultaneously **with the same data**.

Some potential use cases:

* Tagging multiple links at once
* Setting the same expiration date for multiple links
* Updating UTM parameters for multiple links

<Warning>
  You cannot update the domain or key of a link with this endpoint. Also,
  [webhook events](/docs/webhooks/introduction) will not be triggered when using
  bulk link updates
</Warning>

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.links.updateMany({
    linkIds: ["clux0rgak00011...", "clux0rgak00022..."],
    data: {
      utm_source: "facebook",
      utm_medium: "cpc",
    },
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.UpdateMany(ctx, operations.BulkUpdateLinksRequestBody{
      LinkIds: []string{
        "clux0rgak00011...",
        "clux0rgak00022...",
      },
      Data: map[string]string{
        "utm_source": "facebook",
        "utm_medium": "cpc",
      },
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.links.update_many(request={
    "link_ids": [
      "clux0rgak00011...",
      "clux0rgak00022...",
    ],
    "data": {
      "utm_source": "facebook",
      "utm_medium": "cpc",
    },
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::BulkUpdateLinksRequestBody.new(
    link_ids: [
      "clux0rgak00011...",
      "clux0rgak00022...",
    ],
    data: {
      "utm_source": "facebook",
      "utm_medium": "cpc",
    },
  )

  res = s.links.update_many(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $response = $sdk->links->updateMany(
    linkIds: [
      'clux0rgak00011...',
      'clux0rgak00022...',
    ],
    data: {
      "utm_source": "facebook",
      "utm_medium": "cpc",
    },
  );
  ```

  ```bash cURL theme={null}
  curl --request PATCH \
    --url https://api.dub.co/links/bulk \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{"link_ids": ["clux0rgak00011...", "clux0rgak00022..."], "data": {"utm_source": "facebook", "utm_medium": "cpc"}}'
  ```
</CodeGroup>

Check out the [full API reference for the link bulk update endpoint](/docs/api-reference/links/bulk-update).

## Bulk delete links

With bulk delete, you can delete up to 100 links at once.

<Warning>
  This is a destructive action and cannot be undone. Proceed with caution. Also,
  [webhook events](/docs/webhooks/introduction) will not be triggered when using
  this endpoint.
</Warning>

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.links.deleteMany({
    linkIds: ["clux0rgak00011...", "clux0rgak00022..."],
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

     res, err := s.Links.DeleteMany(ctx, operations.BulkDeleteLinksRequest{
      LinkIds: []string{
        "clux0rgak00011...",
        "clux0rgak00022...",
      },
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

   res = dub.links.delete_many(request={
    "link_ids": [
      "clux0rgak00011...",
      "clux0rgak00022...",
    ],
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::BulkDeleteLinksRequest.new(
    link_ids: [
      "clux0rgak00011...",
      "clux0rgak00022...",
    ],
  )

  res = s.links.delete_many(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();


  $response = $sdk->links->deleteMany(
    linkIds: [
      'clux0rgak00011...',
      'clux0rgak00022...',
    ]
  );

  ```

  ```bash cURL theme={null}
  curl --request DELETE \
    --url https://api.dub.co/links/bulk?linkIds=clux0rgak00011... \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

Check out the [full API reference for the link bulk delete endpoint](/docs/api-reference/links/bulk-delete).


# Introduction
Source: https://dub.co/docs/concepts/links/introduction

Learn how to use Dub to programmatically create, update, and delete links at scale.

Links are the bread and butter of [Dub](https://dub.co).

Everything on Dub starts with a link. Whether you're creating:

* a handful of links for your marketing campaign
* hundreds of links for your [affiliate program](https://dub.co/partners)
* thousands of links, [programmatically](/docs/api-reference/links/bulk-create), for your SMS campaign

In this guide, we'll cover the link model, how to create links, and more.

## The link model

The link model consists of the following properties:

| Property    | Description                                               | Example                          |
| ----------- | :-------------------------------------------------------- | :------------------------------- |
| `id`        | The unique identifier of the link (prefixed with `link_`) | `link_1KJ1H3PZGE081A22TC6PTQYJP` |
| `url`       | The destination URL of the link                           | `https://dub.co/home`            |
| `shortLink` | The shortened version of the link (including https)       | `https://dub.link/claim`         |
| `domain`    | The domain of the link                                    | `dub.link`                       |
| `key`       | The short link slug                                       | `claim`                          |

For more advanced features like [custom link previews](/help/article/custom-link-previews), [conversion tracking](/docs/concepts/attribution), and more, see the full list of properties [here](/docs/api-reference/links/create).

You can use the various [Dub SDKs](/docs/sdks/overview) to programmatically manage your links.

## Create a link

The `url` field, representing the destination URL, is the sole mandatory parameter required for the creation of a new short link.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.create({
    url: "https://google.com",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{
      URL: "https://google.com",
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.create(request={
    "url": "https://google.com",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://google.com",
  )
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\CreateLinkRequestBody(
    url: 'https://google.com',
  );

  $response = $sdk->links->create(
    request: $request
  );
  ```

  ```bash cURL {5} theme={null}
  curl --request POST \
    --url https://api.dub.co/links \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{"url": "https://google.com"}'
  ```
</CodeGroup>

Check out the [full API reference for the link creation endpoint](/docs/api-reference/links/create).

## Update a link

An existing link can be updated by providing the `id` to the `update` method. This method returns the updated link as a response.

You can use either the `linkId` or an `externalId` prefixed with `ext_` which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.update("link_1KJ1H3PZGE081A22TC6PTQYJP", {
    url: "https://www.google.uk", // new URL
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Update(ctx, "link_1KJ1H3PZGE081A22TC6PTQYJP", &operations.UpdateLinkRequestBody{
      URL: "https://www.google.uk", // new URL
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.links.update(link_id="link_1KJ1H3PZGE081A22TC6PTQYJP", request_body={
    "url": "https://www.google.uk", // new URL
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  res = s.links.update(link_id="link_1KJ1H3PZGE081A22TC6PTQYJP", request_body={
    "url": "https://www.google.uk", // new URL
  })
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $requestBody = new Operations\UpdateLinkRequestBody(
    url: 'https://www.google.uk', // new URL
  );

  $response = $sdk->links->update(
    linkId: 'link_1KJ1H3PZGE081A22TC6PTQYJP',
    requestBody: $requestBody
  );
  ```

  ```bash cURL theme={null}
  curl --request PATCH \
    --url https://api.dub.co/links/link_1KJ1H3PZGE081A22TC6PTQYJP \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{"url": "https://www.google.uk"}'
  ```
</CodeGroup>

Check out the [full API reference for the link update endpoint](/docs/api-reference/links/update).

## Upsert a link

Upserting a link is a combination of creating and updating a link. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.

This allows you to use the upsert method without the necessity of checking for the link's existence beforehand.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.upsert({
    url: "https://google.com", // will always be the same short link
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Upsert(ctx, &operations.UpsertLinkRequestBody{
      URL: "https://google.com",
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.upsert(request={
    "url": "https://google.com",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::UpsertLinkRequestBody.new(
    url: "https://google.com",
  )
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\UpsertLinkRequestBody(
    url: 'https://google.com',
  );

  $response = $sdk->links->upsert(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.dub.co/links/upsert \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{"url": "https://google.com"}'
  ```
</CodeGroup>

Check out the [full API reference for the link upsert endpoint](/docs/api-reference/links/upsert).


# Organizing links
Source: https://dub.co/docs/concepts/links/organization

Learn how to associate links with users, campaigns, teams, and other entities within your system.

When creating links programmatically with Dub, you might want a way to associate them with a user or other identifiers in your system.

There are a few ways to do this, depending on your data structure:

| Method                      | Type         | Description                                        | Use case                                                                                              |
| --------------------------- | ------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| [External ID](#external-id) | One-to-one   | A unique identifier for a link within your system. | Associating referral links with users in your system.                                                 |
| [Tenant ID](#tenant-id)     | One-to-many  | The ID of the tenant that created the link.        | Grouping all links created by a user/team in your system.                                             |
| [Tags](#tags)               | Many-to-many | Grouping links by tags                             | Organizing links by campaign / user / various for flexible, multi-dimensional filtering and reporting |
| [Folders](#folders)         | One-to-many  | The ID of the folder that contains the link.       | Organizing links into hierarchical folder structures for better organization.                         |

## External ID

In certain scenarios, it is essential to identify links within your system. For instance, you may need to associate a link with a user without storing the Dub link ID directly in your database (e.g. for referral links).

The `externalId` field serves this purpose effectively. It acts as a unique identifier within your database, allowing you to associate it with a corresponding link in Dub's system.

Dub allows you to create links using an `externalId` and subsequently retrieve them by the same identifier.

<Note>
  `externalId` should be a unique value across your workspace. Trying to create
  a link with an externalId that already exists will result in a [`409` conflict
  error](/docs/api-reference/introduction#conflict) error.
</Note>

### Create link with an externalId

Here is an example of how to create a link with an `externalId`:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.create({
    url: "https://google.com",
    externalId: "12345",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{
      URL: "https://google.com",
      ExternalId: "12345",
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.create(request={
    "url": "https://google.com",
    "external_id": "12345",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://google.com",
    external_id: "12345",
  )
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\CreateLinkRequestBody(
    url: 'https://google.com',
    externalId: '12345',
  );

  $response = $sdk->links->create(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.dub.co/links \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
      "url": "https://google.com",
      "external_id": "12345"
    }'
  ```
</CodeGroup>

### Retrieve link by externalId

Let's see how to retrieve a link by its `externalId`:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.get({
    externalId: "12345",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Get(ctx, operations.GetLinkInfoRequest{
      ExternalID: dubgo.String("12345"),
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.get(request={
    "external_id": "12345",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::GetLinkInfoRequest.new(
    external_id: "12345",
  )

  res = s.links.get(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $response = $sdk->links->get(
    externalId: '12345'
  );
  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/links/info?external_id=12345 \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

### Update link by externalId

In addition to updating a link by its `linkId`, you can also update a link by its `externalId`.

<Note>
  Make sure to prefix the `externalId` with `ext_`. For example, if your
  `externalId` is `12345`, you should pass `ext_12345`.
</Note>

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.update("ext_12345", {
    url: "https://www.google.uk", // new URL
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Update(ctx, "ext_12345", &operations.UpdateLinkRequestBody{
      URL: "https://www.google.uk", // new URL
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.links.update(link_id="ext_12345", request_body={
    "url": "https://www.google.uk", // new URL
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  res = s.links.update(link_id="ext_12345", request_body={
    "url": "https://www.google.uk", // new URL
  })
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $requestBody = new Operations\UpdateLinkRequestBody(
    url: 'https://www.google.uk', // new URL
  );

  $response = $sdk->links->update(
    linkId: 'ext_12345',
    requestBody: $requestBody
  );
  ```

  ```bash cURL theme={null}
  curl --request PATCH \
    --url https://api.dub.co/links/ext_12345 \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{"url": "https://www.google.uk"}'
  ```
</CodeGroup>

### Retrieve analytics by externalId

You can also retrieve analytics for a link by its `externalId`. This is helpful for fetching the analytics for a given link using the unique identifier from your system.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const analytics = await dub.analytics.retrieve({
    externalId: "ext_12345",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
      ExternalID: dubgo.String("ext_12345"),
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.analytics.retrieve(request={
    "external_id": "ext_12345",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    external_id: "ext_12345",
  )

  res = s.analytics.retrieve(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\RetrieveAnalyticsRequest(
    externalId: "ext_12345",
  );

  $response = $sdk->analytics->retrieve(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/analytics?external_id=ext_12345 \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

## Tenant ID

The ID of the tenant that created the link inside your system. If set, it can be used to fetch all links for a tenant. This is useful if you have a system that lets your users create their own links, and you want to group them on a tenant level.

### Create link with tenantId

Let's see how to create a link with a tenant ID:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.create({
    url: "https://google.com",
    tenantId: "12345",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{
      URL: "https://google.com",
      TenantId: "12345",
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.create(request={
    "url": "https://google.com",
    "tenant_id": "12345",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://google.com",
    tenant_id: "12345",
  )
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\CreateLinkRequestBody(
    url: 'https://google.com',
    tenantId: '12345',
  );

  $response = $sdk->links->create(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.dub.co/links \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
      "url": "https://google.com",
      "external_id": "12345"
    }'
  ```
</CodeGroup>

### Retrieve links by tenantId

Here is how to retrieve links by tenant ID:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.links.list({
    tenantId: "12345",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.List(ctx, operations.GetLinksRequest{
      TenantId: dubgo.String("12345"),
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = s.links.list(request={
    "tenant_id": "12345",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::GetLinksRequest.new(
    tenant_id: "12345",
  )

  res = s.links.list(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\GetLinksRequest(
    tenantId: "12345",
  );

  $responses = $sdk->links->list(
    request: $request
  );

  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/links?tenantId=12345 \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

### Retrieve analytics by tenantId

You can retrieve analytics by tenantId by passing the `tenantId` prop. This is helpful for fetching the analytics for all the links under a specific tenant, or the total analytics for a tenant.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const analytics = await dub.analytics.retrieve({
    tenantId: "12345",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
      TenantId: dubgo.String("12345"),
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.analytics.retrieve(request={
    "tenant_id": "12345",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    tenant_id: "12345",
  )

  res = s.analytics.retrieve(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\RetrieveAnalyticsRequest(
    tenantId: "12345",
  );

  $response = $sdk->analytics->retrieve(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/analytics?tenantId=12345 \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

***

## Tags

Tags are a great way to organize your links on Dub.

With tags, you can:

* Organize your links by campaigns, clients, or any other categories you can think of.
* [Filter your links by tags](#retrieve-links-by-tags) and get a shareable link to the filtered results.
* [Filter your analytics by tags](#retrieve-analytics-by-tags) to see how your campaigns are performing.

### Create link with tags

You can use either pass the tag ID or tag name to create a link with tags.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.create({
    url: "https://example.com",
    tagIds: ["clux0rgak00011..."],
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{
      URL: "https://example.com",
      TagIds: []string{"clux0rgak00011..."},
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.create(request={
    "url": "https://example.com",
    "tag_ids": ["clux0rgak00011..."],
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://example.com",
    tag_ids: ["clux0rgak00011..."],
  )
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\CreateLinkRequestBody(
    url: 'https://example.com',
    tagIds: ['clux0rgak00011...'],
  );

  $response = $sdk->links->create(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.dub.co/links \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
      "url": "https://example.com",
      "tagIds": ["clux0rgak00011..."]
    }'
  ```
</CodeGroup>

### Retrieve links by tags

You can retrieve links by tag by tags.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.links.list({
    tagNames: ["tag1"],
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.List(ctx, operations.GetLinksRequest{
      TagNames: []string{"tag1"},
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = s.links.list(request={
    "tag_names": ["tag1"],
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::GetLinksRequest.new(
    tag_names: ["tag1"],
  )

  res = s.links.list(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\GetLinksRequest(
    tagNames: ["tag1"],
  );

  $responses = $sdk->links->list(
    request: $request
  );

  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/links?tagNames=tag1 \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

### Retrieve analytics by tags

You can retrieve analytics for a tag (or multiple tags) by passing the `tagIds` prop. This is helpful for fetching the analytics for all the links under a specific tag, or the total analytics for a tag (or multiple tags).

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const analytics = await dub.analytics.retrieve({
    tagIds: ["tag_xxx"],
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
      TagIds: []string{"tag_xxx"},
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.analytics.retrieve(request={
    "tag_ids": ["tag_xxx"],
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    tag_ids: ["tag_xxx"],
  )

  res = s.analytics.retrieve(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\RetrieveAnalyticsRequest(
    tagIds: ["tag_xxx"],
  );

  $response = $sdk->analytics->retrieve(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/analytics?tagIds=tag_xxx \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

***

## Folders

Folders are a hierarchical way to organize your links on Dub.

With folders, you can:

* Organize your links into directories
* [Filter your links by folder](#retrieve-links-by-folders) and get a shareable link to the filtered results.
* [Filter your analytics by folder](#retrieve-analytics-by-folders) to see how your campaigns are performing.

### Create link with folderId

You can use the folder ID to create a link with a folder.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const link = await dub.links.create({
    url: "https://example.com",
    folderId: "clux0rgak00011...",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{
      URL: "https://example.com",
      FolderId: "clux0rgak00011...",
    })

    if err != nil {
      log.Fatal(err)
    }

    if res != nil {
      // handle response
    }
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = d.links.create(request={
    "url": "https://example.com",
    "folder_id": "clux0rgak00011...",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://example.com",
    folder_id: "clux0rgak00011...",
  )
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\CreateLinkRequestBody(
    url: 'https://example.com',
    folderId: 'clux0rgak00011...',
  );

  $response = $sdk->links->create(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.dub.co/links \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
      "url": "https://example.com",
      "folderId": "clux0rgak00011..."
    }'
  ```
</CodeGroup>

### Retrieve links by folderId

Here is how to retrieve links by folder ID:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const result = await dub.links.list({
    folderId: "clux0rgak00011...",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Links.List(ctx, operations.GetLinksRequest{
      FolderId: dubgo.String("clux0rgak00011..."),
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = s.links.list(request={
    "folder_id": "clux0rgak00011...",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::GetLinksRequest.new(
    folder_id: "clux0rgak00011...",
  )

  res = s.links.list(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\GetLinksRequest(
    folderId: "clux0rgak00011...",
  );

  $responses = $sdk->links->list(
    request: $request
  );

  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/links?folderId=clux0rgak00011... \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>

### Retrieve analytics by folderId

You can retrieve analytics by folderId by passing the `folderId` prop. This is helpful for fetching the analytics for all the links under a specific folder, or the total analytics for a folder.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  export const dub = new Dub({
    token: process.env.DUB_API_KEY,
  });

  const analytics = await dub.analytics.retrieve({
    folderId: "clux0rgak00011...",
  });
  ```

  ```go Go theme={null}
  package main

  import(
    "context"
    dubgo "github.com/dubinc/dub-go"
    "github.com/dubinc/dub-go/models/operations"
    "log"
    "os"
  )

  func main() {
    ctx := context.Background()

    s := dubgo.New(
      dubgo.WithSecurity(os.Getenv("DUB_API_KEY")),
    )

    res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{
      FolderId: dubgo.String("clux0rgak00011..."),
    })
  }
  ```

  ```python Python theme={null}
  import os
  import dub
  from dub.models import operations

  d = dub.Dub(
    token=os.environ['DUB_API_KEY'],
  )

  res = dub.analytics.retrieve(request={
    "folder_id": "clux0rgak00011...",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new
  s.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: "DUB_API_KEY",
    )
  )

  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    folder_id: "clux0rgak00011...",
  )

  res = s.analytics.retrieve(req)
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build();

  $request = new Operations\RetrieveAnalyticsRequest(
    folderId: "clux0rgak00011...",
  );

  $response = $sdk->analytics->retrieve(
    request: $request
  );
  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url https://api.dub.co/analytics?folderId=clux0rgak00011... \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json'
  ```
</CodeGroup>


# Appwrite
Source: https://dub.co/docs/conversions/leads/appwrite

Learn how to track lead conversion events with Appwrite and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Appwrite for user authentication.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Configure Appwrite

Next, configure Appwrite to track lead conversion events during the sign up process.

<Steps>
  <Step title="Create an Appwrite Cloud project">
    Head to [Appwrite Cloud](https://apwr.dev/appwrite-dub) and create a new project.

    <Frame>
      <img alt="New project on Appwrite Cloud" />
    </Frame>

    Create a new API key with the `sessions.write` scope enabled and save the API key for later use. You can also copy your project ID and endpoint from the project's Settings page.

    <Frame>
      <img alt="API key in your project on Appwrite Cloud" />
    </Frame>

    Then, in your Next.js app, install the Appwrite Node.js SDK.

    ```bash theme={null}
    npm i node-appwrite
    ```
  </Step>

  <Step title="Add environment variables">
    Add the following environment variables to your app.

    ```bash theme={null}
    NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
    NEXT_PUBLIC_APPWRITE_PROJECT=<APPWRITE_PROJECT_ID>
    NEXT_APPWRITE_KEY=<APPWRITE_API_KEY>
    NEXT_DUB_API_KEY=<DUB_API_KEY>
    ```
  </Step>

  <Step title="Integrate Dub Analytics">
    Add the `DubAnalytics` component from the `@dub/analytics` package to your app’s root layout.

    ```tsx src/app/layout.tsx theme={null}
    import type { Metadata } from 'next';
    import { Analytics as DubAnalytics } from '@dub/analytics/react';

    export const metadata: Metadata = {
      title: 'Appwrite Dub Leads Example',
      description: 'Appwrite Dub Leads Tracking example app with Next.js'
    };

    export default function RootLayout({
      children
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <body>{children}</body>
          <DubAnalytics />
        </html>
      );
    }
    ```
  </Step>

  <Step title="Prepare the Appwrite client and auth library">
    Create the Appwrite Session and Admin client (necessary for SSR apps, as explained in the [Appwrite docs](https://appwrite.io/docs/products/auth/server-side-rendering)). Additionally, create a function to verify user login.

    ```ts src/lib/server/appwrite.ts theme={null}
    'use server';
    import { Client, Account } from 'node-appwrite';
    import { cookies } from 'next/headers';

    export async function createSessionClient() {
      const client = new Client()
        .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string)
        .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string);

      const session = (await cookies()).get('my-custom-session');
      if (!session || !session.value) {
        throw new Error('No session');
      }

      client.setSession(session.value);

      return {
        get account() {
          return new Account(client);
        }
      };
    }

    export async function createAdminClient() {
      const client = new Client()
        .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string)
        .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string)
        .setKey(process.env.NEXT_APPWRITE_KEY as string);

      return {
        get account() {
          return new Account(client);
        }
      };
    }
    ```
  </Step>

  <Step title="Set up Dub SDK">
    Create the Dub client and send leads to Dub using the `dub.track.lead()` function.

    ```ts src/lib/server/dub.ts theme={null}
    import type { Models } from 'node-appwrite';
    import { Dub } from 'dub';

    const dub = new Dub({
      token: process.env.NEXT_DUB_API_KEY
    });

    export function addDubLead(user: Models.User<Models.Preferences>, dub_id: string) {
      dub.track.lead({
        clickId: dub_id,
        eventName: 'Sign Up',
        customerExternalId: user.$id,
        customerName: user.name,
        customerEmail: user.email
      });
    }
    ```
  </Step>

  <Step title="Send leads to Dub on user signup">
    In the `/auth` page, use the Appwrite Admin client to allow users to sign up. Post sign up, check if the `dub_id` cookie is present, send a lead event to Dub if found, and delete the `dub_id` cookie.

    ```tsx src/app/auth/page.tsx theme={null}
    import { ID } from 'node-appwrite';
    import { createAdminClient, getLoggedInUser } from '@/lib/server/appwrite';
    import { cookies } from 'next/headers';
    import { redirect } from 'next/navigation';
    import { addDubLead } from '@/lib/server/dub';

    async function signUpWithEmail(formData: any) {
      'use server';

      // Get sign up info from form
      const email = formData.get('email');
      const password = formData.get('password');
      const name = formData.get('name');

      // Create account and session using Appwrite
      const { account } = await createAdminClient();

      const user = await account.create(ID.unique(), email, password, name);
      const session = await account.createEmailPasswordSession(email, password);

      (await cookies()).set('my-custom-session', session.secret, {
        path: '/',
        httpOnly: true,
        sameSite: 'strict',
        secure: true
      });

      // Check if Dub ID is present in cookies and track lead if found
      const dub_id = (await cookies()).get('dub_id')?.value;
      if (dub_id) {
        addDubLead(user, dub_id);
        (await cookies()).delete('dub_id');
      }

      // Redirect to success page
      redirect('/auth/success');
    }

    export default async function SignUpPage() {

      // Verify active user session and redirect to success page if found
      const user = await getLoggedInUser();
      if (user) redirect('/auth/success');

      return (
        <>
          <form action={signUpWithEmail}>
            <input id="email" name="email" placeholder="Email" type="email" required />
            <input id="password" name="password" placeholder="Password" minLength={8} type="password" required />
            <input id="name" name="name" placeholder="Name" type="text" required />
            <button type="submit">Sign up</button>
          </form>
        </>
      );
    }
    ```
  </Step>
</Steps>

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

## Example App

To learn more about how to track leads with Appwrite, check out the following example app:

<Card title="Appwrite + Dub Next.js  Example" icon="github" href="https://github.com/appwrite-community/appwrite-dub-next">
  See how to track new user sign-ups with Appwrite and the Dub SDK.
</Card>

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Auth0
Source: https://dub.co/docs/conversions/leads/auth0

Learn how to track lead conversion events with Auth0 and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Auth0 for user authentication.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Configure Auth0

Next, configure Auth0 to track lead conversion events.

Here's how it works in a nutshell:

1. In the sign in `afterCallback` function, check if the user is a new sign up.
2. If the user is a new sign up, check if the `dub_id` cookie is present.
3. If the `dub_id` cookie is present, send a lead event to Dub using `dub.track.lead`
4. Delete the `dub_id` cookie.

```typescript app/api/auth/[auth0]/route.js theme={null}
import { handleAuth, handleCallback, type Session } from "@auth0/nextjs-auth0";
import { cookies } from "next/headers";
import { dub } from "@/lib/dub";

const afterCallback = async (req: Request, session: Session) => {
  const userExists = await getUser(session.user.email);

  if (!userExists) {
    createUser(session.user);
    // check if dub_id cookie is present
    const clickId = cookies().get("dub_id")?.value;
    if (clickId) {
      // send lead event to Dub
      await dub.track.lead({
        clickId,
        eventName: "Sign Up",
        customerExternalId: session.user.id,
        customerName: session.user.name,
        customerEmail: session.user.email,
        customerAvatar: session.user.image,
      });
      // delete the dub_id cookie
      cookies().set("dub_id", "", {
        expires: new Date(0),
      });
    }
    return session;
  }
};

export default handleAuth({
  callback: handleCallback({ afterCallback }),
});
```

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Better Auth
Source: https://dub.co/docs/conversions/leads/better-auth

Learn how to track lead conversion events with Better Auth and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Better Auth for user authentication.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Installation

<Steps>
  <Step title="Install the @dub/better-auth plugin">
    To get started, simply install the [`@dub/better-auth` plugin](https://www.npmjs.com/package/@dub/better-auth) via your preferred package manager:

    <CodeGroup>
      ```bash npm theme={null}
      npm install @dub/better-auth
      ```

      ```bash yarn theme={null}
      yarn add @dub/better-auth
      ```

      ```bash pnpm theme={null}
      pnpm add @dub/better-auth
      ```

      ```bash bun theme={null}
      bun add @dub/better-auth
      ```
    </CodeGroup>
  </Step>

  <Step title="Configure the plugin">
    Then, add the plugin to your better-auth config file:

    ```ts auth.ts theme={null}
    import { dubAnalytics } from "@dub/better-auth";
    import { betterAuth } from "better-auth";
    import { Dub } from "dub";

    const dub = new Dub();

    export const auth = betterAuth({
      plugins: [
        dubAnalytics({
          dubClient: dub,
        }),
      ],
    });
    ```
  </Step>
</Steps>

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Clerk
Source: https://dub.co/docs/conversions/leads/clerk

Learn how to track lead conversion events with Clerk and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Clerk for user authentication.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Configure Clerk

Next, configure Clerk to track lead conversion events when a new user signs up. Here's a quick video showing how to do this:

<iframe title="Loom video player" />

Here's a quick summary of the steps:

<Steps>
  <Step title="Add environment variables">
    Add the following environment variables to your app:

    ```bash theme={null}
    # get it here: https://dashboard.clerk.com/apps/new
    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key
    CLERK_SECRET_KEY=your_secret_key

    # get it here: https://d.to/tokens
    DUB_API_KEY=your_api_key
    ```
  </Step>

  <Step title="Add a custom claim to your Clerk session token">
    Add the following JSON as a [custom claim](https://clerk.com/docs/references/nextjs/add-onboarding-flow#add-custom-claims-to-your-session-token) to your Clerk session token:

    ```json Clerk Session Token theme={null}
    {
      "metadata": "{{user.public_metadata}}"
    }
    ```
  </Step>

  <Step title="Extend the `@dub/analytics` package with Clerk's `useUser` hook">
    Extend the [`@dub/analytics` package](/docs/sdks/client-side/introduction) to include a `trackLead` server action.

    ```tsx components/dub-analytics.tsx theme={null}
    "use client";

    import { trackLead } from "@/actions/track-lead";
    import { useUser } from "@clerk/nextjs";
    import { Analytics, AnalyticsProps } from "@dub/analytics/react";
    import { useEffect } from "react";

    export function DubAnalytics(props: AnalyticsProps) {
      const { user } = useUser();

      useEffect(() => {
        if (!user || user.publicMetadata.dubClickId) return;

        // if the user is loaded but hasn't been persisted to Dub yet, track the lead event
        trackLead({
          id: user.id,
          name: user.fullName!,
          email: user.primaryEmailAddress?.emailAddress,
          avatar: user.imageUrl,
        }).then(async (res) => {
          if (res.ok) await user.reload();
          else console.error(res.error);
        });

        // you can also use an API route instead of a server action
        /*
        fetch("/api/track-lead", {
          method: "POST",
          body: JSON.stringify({
            id: user.id,
            name: user.fullName,
            email: user.primaryEmailAddress?.emailAddress,
            avatar: user.imageUrl,
          }),
        }).then(res => {
          if (res.ok) await user.reload();
          else console.error(res.statusText);
        });
        */
      }, [user]);

      return <Analytics {...props} />;
    }
    ```

    Then, add the `DubAnalytics` component to your app's root layout component:

    ```tsx app/layout.tsx theme={null}
    import { DubAnalytics } from "@/components/dub-analytics";

    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html>
          <body>
            <DubAnalytics />
            {children}
          </body>
        </html>
      );
    }
    ```
  </Step>

  <Step title="Implement the `trackLead` server action">
    On the server side, implement the `trackLead` server action. Alternatively, you can also create an API route instead:

    <CodeGroup>
      ```tsx /actions/track-lead.ts theme={null}
      // This is a server action
      "use server";

      import { dub } from "@/lib/dub";
      import { clerkClient } from "@clerk/nextjs/server";
      import { cookies } from "next/headers";

      export async function trackLead({
        id,
        name,
        email,
        avatar,
      }: {
        id: string;
        name?: string | null;
        email?: string | null;
        avatar?: string | null;
      }) {
        try {
          const cookieStore = await cookies();
          const dubId = cookieStore.get("dub_id")?.value;

          if (dubId) {
            // Send lead event to Dub
            await dub.track.lead({
              clickId: dubId,
              eventName: "Sign Up",
              customerExternalId: id,
              customerName: name,
              customerEmail: email,
              customerAvatar: avatar,
            });

            // Delete the dub_id cookie
            cookieStore.set("dub_id", "", {
              expires: new Date(0),
            });
          }

          const clerk = await clerkClient();
          await clerk.users.updateUser(id, {
            publicMetadata: {
              dubClickId: dubId || "n/a",
            },
          });

          return { ok: true };
        } catch (error) {
          console.error("Error in trackLead:", error);
          return { ok: false, error: (error as Error).message };
        }
      }
      ```

      ```tsx /api/track-lead/route.ts theme={null}
      // This is an API route
      import { NextRequest, NextResponse } from "next/server";

      export async function POST(req: NextRequest) {
        // read dub_id from the request cookies
        const dubId = req.cookies.get("dub_id")?.value;
        if (dubId) {
          // Send lead event to Dub
          await dub.track.lead({
            clickId: dubId,
            eventName: "Sign Up",
            customerExternalId: id,
            customerName: name,
            customerEmail: email,
            customerAvatar: avatar,
          });
        }

        const clerk = await clerkClient();
        await clerk.users.updateUser(id, {
          publicMetadata: {
            dubClickId: dubId || "n/a",
          },
        });
        const res = NextResponse.json({ ok: true });
        // Delete the dub_id cookie
        res.cookies.set("dub_id", "", {
          expires: new Date(0),
        });
        return res;
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

## Example App

To learn more about how to track leads with Clerk, check out the following example app:

<Card title="Dub + Clerk Example App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/clerk">
  See how to track new user sign-ups with Clerk and the Dub SDK.
</Card>

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Deferred lead tracking
Source: https://dub.co/docs/conversions/leads/deferred

Learn how to track a deferred lead conversion event with Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

However, there are times where signups alone might not be the clearest indicator of a lead conversion event. For instance, you might want to track a more meaningful lead event such as:

* A user completing their first meeting on [Granola](https://partners.dub.co/granola)
* A user making their first search query on [Perplexity](https://partners.dub.co/perplexity)
* A user dictating their first 2,000 words on [Wispr Flow](https://partners.dub.co/flow)

In these cases, you can use deferred lead tracking to defer the actual lead event creation to a subsequent request:

<Frame>
  <img alt="A diagram showing how deferred lead tracking works" />
</Frame>

<Tip>
  Deferred lead tracking is particularly useful for tracking **sales-qualified
  leads (SQLs)** – both for marketing attribution purposes, as well as to make
  sure that you're [rewarding partners](/help/article/partner-rewards) for
  qualified leads (instead of just pure signups) with [Dub
  Partners](https://dub.co/partners).
</Tip>

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Step 1: Track a deferred lead event

First, when the user completes the action that indicates interest in your product or service, you'll need to track a deferred lead event. Examples include:

* Signing up for an account
* [Booking a demo meeting on HubSpot](/docs/integrations/hubspot)
* Joining a mailing list

To do this, you'll need to set the `mode` property to `deferred` when tracking the lead event. With this, Dub will still track the customer and the click ID they came from, but defer the actual lead event creation to a subsequent request.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  const dub = new Dub();

  const dubId = req.cookies["dub_id"];
  if (dubId) {
    await dub.track.lead({
      clickId: dubId,
      mode: "deferred",
      eventName: "Sign Up",
      customerExternalId: customer.id,
      customerName: customer.name,
      customerEmail: customer.email,
      customerAvatar: customer.avatar,
    });
    // delete the dub_id cookie
    res.cookies.set("dub_id", "", {
      expires: new Date(0),
    });
  }
  ```

  ```python Python theme={null}
  from dub import Dub
  import os

  dub = Dub(token=os.environ['DUB_API_KEY'])

  dub_id = request.cookies.get('dub_id')
  if dub_id:
      dub.track.lead({
          'click_id': dub_id,
          'mode': 'deferred',
          'event_name': 'Sign Up',
          'external_id': customer.id,
          'customer_name': customer.name,
          'customer_email': customer.email,
          'customer_avatar': customer.avatar
      })
      # delete the dub_id cookie
      response.delete_cookie('dub_id')
  ```

  ```go Go theme={null}
  package main

  import (
      "context"
      dub "github.com/dubinc/dub-go"
      "net/http"
  )

  d := dub.New(
      dub.WithSecurity(os.Getenv("DUB_API_KEY")),
  )

  dubId, err := r.Cookie("dub_id")
  if err == nil {
      _, err = d.Track.Lead(context.Background(), &operations.TrackLeadRequest{
          ClickId:         dubId.Value,
          Mode:            "deferred",
          EventName:       "Sign Up",
          customerExternalId:      customer.ID,
          CustomerName:    customer.Name,
          CustomerEmail:   customer.Email,
          CustomerAvatar:  customer.Avatar,
      })
      // delete the dub_id cookie
      http.SetCookie(w, &http.Cookie{
          Name:    "dub_id",
          Value:   "",
          Expires: time.Unix(0, 0),
      })
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  dub = ::OpenApiSDK::Dub.new
  dub.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: ENV['DUB_API_KEY']
    )
  )

  dub_id = cookies[:dub_id]
  if dub_id
    req = ::OpenApiSDK::Operations::TrackLeadRequest.new(
      click_id: dub_id,
      mode: 'deferred',
      event_name: 'Sign Up',
      external_id: customer.id,
      customer_name: customer.name,
      customer_email: customer.email,
      customer_avatar: customer.avatar
    )
    dub.track.lead(req)
    # delete the dub_id cookie
    cookies.delete(:dub_id)
  end
  ```

  ```php PHP theme={null}
  <?php

  require 'vendor/autoload.php';

  use Dub\Dub;
  use Dub\Models\Operations;

  $dub = Dub::builder()->setSecurity($_ENV["DUB_API_KEY"])->build();

  $dubId = $_COOKIE['dub_id'] ?? null;
  if ($dubId) {
      $request = new Operations\TrackLeadRequest();
      $request->clickId = $dubId;
      $request->mode = 'deferred';
      $request->eventName = 'Sign Up';
      $request->customerExternalId = $customer->id;
      $request->customerNasme = $customer->name;
      $request->customerEmail = $customer->email;
      $request->customerAvatar = $customer->avatar;

      $dub->track->lead($request);
      // delete the dub_id cookie
      setcookie('dub_id', '', time() - 3600);
  }
  ```
</CodeGroup>

## Step 2: Track a qualified lead event

Once the user completes the action that makes them a qualified lead, you can then track a qualified lead event. To do this, you'll repeat the same lead tracking request as before, but without the `mode` property and by setting the `clickId` property to an empty string.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  const dub = new Dub();

  await dub.track.lead({
    clickId: "",
    eventName: "Sign Up",
    customerExternalId: customer.id,
    customerName: customer.name,
    customerEmail: customer.email,
    customerAvatar: customer.avatar,
  });
  ```

  ```python Python theme={null}
  from dub import Dub
  import os

  dub = Dub(token=os.environ['DUB_API_KEY'])

  dub.track.lead({
      'click_id': '',
      'event_name': 'Sign Up',
      'external_id': customer.id,
      'customer_name': customer.name,
      'customer_email': customer.email,
      'customer_avatar': customer.avatar
  })
  ```

  ```go Go theme={null}
  package main

  import (
      "context"
      dub "github.com/dubinc/dub-go"
      "net/http"
  )

  d := dub.New(
      dub.WithSecurity(os.Getenv("DUB_API_KEY")),
  )

  d.Track.Lead(context.Background(), &operations.TrackLeadRequest{
      ClickId:         "",
      EventName:       "Sign Up",
      customerExternalId:      customer.ID,
      CustomerName:    customer.Name,
      CustomerEmail:   customer.Email,
      CustomerAvatar:  customer.Avatar,
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  dub = ::OpenApiSDK::Dub.new
  dub.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: ENV['DUB_API_KEY']
    )
  )

  req = ::OpenApiSDK::Operations::TrackLeadRequest.new(
      click_id: '',
      event_name: 'Sign Up',
      external_id: customer.id,
      customer_name: customer.name,
      customer_email: customer.email,
      customer_avatar: customer.avatar
  )
  dub.track.lead(req)
  ```

  ```php PHP theme={null}
  <?php

  require 'vendor/autoload.php';

  use Dub\Dub;
  use Dub\Models\Operations;

  $dub = Dub::builder()->setSecurity($_ENV["DUB_API_KEY"])->build();

  $request = new Operations\TrackLeadRequest();
  $request->clickId = '';
  $request->eventName = 'Sign Up';
  $request->customerExternalId = $customer->id;
  $request->customerNasme = $customer->name;
  $request->customerEmail = $customer->email;
  $request->customerAvatar = $customer->avatar;

  $dub->track->lead($request);
  ```
</CodeGroup>

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# NextAuth.js
Source: https://dub.co/docs/conversions/leads/next-auth

Learn how to track lead conversion events with NextAuth.js and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses NextAuth.js for user authentication.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Configure NextAuth.js Options

Then, set up your NextAuth.js configuration options to track lead conversion events using the `dub` TypeScript SDK.

Here's how it works in a nutshell:

1. Use NextAuth's [`signIn` event](https://next-auth.js.org/configuration/events#signin) to detect when there's a new sign up.
2. If the user is a new sign up, check if the `dub_id` cookie is present.
3. If the `dub_id` cookie is present, send a lead event to Dub using `dub.track.lead`
4. Delete the `dub_id` cookie.

Under the hood, Dub records the user as a customer and associates them with the click event that they came from. The user's unique ID is now the source of truth for all future events – hence why we don't need the `dub_id` cookie anymore.

<CodeGroup>
  ```typescript App Router theme={null}
  // app/api/auth/[...nextauth]/options.ts
  import type { NextAuthOptions } from "next-auth";
  import { cookies } from "next/headers";
  import { dub } from "@/lib/dub";

  export const authOptions: NextAuthOptions = {
    ...otherAuthOptions, // your other NextAuth options
    events: {
      async signIn(message) {
        // if it's a new sign up
        if (message.isNewUser) {
          const cookieStore = await cookies();
          // check if dub_id cookie is present
          const dub_id = cookieStore.get("dub_id")?.value;
          if (dub_id) {
            // send lead event to Dub
            await dub.track.lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerExternalId: user.id,
              customerName: user.name,
              customerEmail: user.email,
              customerAvatar: user.image,
            });
            // delete the cookies
            cookieStore.delete("dub_id");
            cookieStore.delete("dub_partner_data");
          }
        }
      },
    },
  };
  ```

  ```typescript Pages Router theme={null}
  // pages/api/auth/[...nextauth]/options.ts
  import type { NextApiRequest } from "next";
  import type { NextAuthOptions } from "next-auth";
  import { dub } from "@/lib/dub";

  export const getOptions = (req: NextApiRequest): NextAuthOptions => ({
    ...otherAuthOptions, // your other NextAuth options
    events: {
      async signIn(message) {
        // if it's a new sign up
        if (message.isNewUser) {
          // check if dub_id cookie is present
          const { dub_id } = req.cookies;
          if (dub_id) {
            // send lead event to Dub
            await dub.track.lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerExternalId: user.id,
              customerName: user.name,
              customerEmail: user.email,
              customerAvatar: user.image,
            });
          }
        }
      },
    },
  });
  ```
</CodeGroup>

<Tip>
  In NextAuth.js, the `isNewUser` flag will [only be available if you're using
  `next-auth`'s database
  implementation](https://next-auth.js.org/configuration/options#callbacks)
  (otherwise it'll return `undefined`). In that case, you should move the logic
  above to the [`signIn`
  `callback`](https://next-auth.js.org/configuration/callbacks) instead.
</Tip>

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

## Create a NextAuth.js Route Handler

Finally, import the `authOptions` variable you created earlier and use `NextAuth` to create a handler for your NextAuth.js routes.

<CodeGroup>
  ```typescript App Router theme={null}
  // app/api/auth/[...nextauth]/index.ts
  import { authOptions } from "./options";
  import NextAuth from "next-auth";

  const handler = NextAuth(authOptions);

  export { handler as GET, handler as POST };
  ```

  ```typescript Pages Router theme={null}
  // pages/api/auth/[...nextauth]/index.ts
  import type { NextApiRequest, NextApiResponse } from "next";
  import NextAuth from "next-auth";
  import { getOptions } from "./options";

  const handler = (req: NextApiRequest, res: NextApiResponse) =>
    NextAuth(req, res, getOptions(req));

  export default handler;
  ```
</CodeGroup>

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Supabase
Source: https://dub.co/docs/conversions/leads/supabase

Learn how to track lead conversion events with Supabase and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Booking a demo meeting
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Supabase for user authentication.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Configure Supabase

Next, configure Supabase to track lead conversion events in the auth callback function.

Here's how it works in a nutshell:

1. In the `/api/auth/callback` route, check if:
   * the `dub_id` cookie is present.
   * the user is a new sign up (created in the last 10 minutes).
2. If the `dub_id` cookie is present and the user is a new sign up, send a lead event to Dub using `dub.track.lead`
3. Delete the `dub_id` cookie.

<CodeGroup>
  ```typescript Next.js App Router theme={null}
  // app/api/auth/callback/route.ts
  import { cookies } from "next/headers";
  import { NextResponse } from "next/server";
  import { createClient } from "@/lib/supabase/server";
  import { waitUntil } from "@vercel/functions";
  import { dub } from "@/lib/dub";

  export async function GET(request: Request) {
    const { searchParams, origin } = new URL(request.url);
    const code = searchParams.get("code");
    // if "next" is in param, use it as the redirect URL
    const next = searchParams.get("next") ?? "/";

    if (code) {
      const supabase = createClient(cookies());
      const { data, error } = await supabase.auth.exchangeCodeForSession(code);
      if (!error) {
        const { user } = data;
        const dub_id = cookies().get("dub_id")?.value;
        // if the user is created in the last 10 minutes, consider them new
        const isNewUser =
          new Date(user.created_at) > new Date(Date.now() - 10 * 60 * 1000);
        // if the user is new and has a dub_id cookie, track the lead
        if (dub_id && isNewUser) {
          waitUntil(
            dub.track.lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerExternalId: user.id,
              customerName: user.user_metadata.name,
              customerEmail: user.email,
              customerAvatar: user.user_metadata.avatar_url,
            }),
          );
          // delete the clickId cookie
          cookies().delete("dub_id");
        }
        return NextResponse.redirect(`${origin}${next}`);
      }
    }

    // return the user to an error page with instructions
    return NextResponse.redirect(`${origin}/auth/auth-code-error`);
  }
  ```

  ```typescript Next.js Pages Router theme={null}
  // pages/api/auth/callback.ts
  import { NextApiRequest, NextApiResponse } from "next";
  import { createClient } from "@supabase/supabase-js";
  import { dub } from "@/lib/dub";

  export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse,
  ) {
    const { code, next = "/" } = req.query;
    const origin = `${req.headers["x-forwarded-proto"]}://${req.headers.host}`;

    if (typeof code === "string") {
      const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL!,
        process.env.SUPABASE_SERVICE_ROLE_KEY!,
      );

      const { data, error } = await supabase.auth.exchangeCodeForSession(code);

      if (!error) {
        const { user } = data;
        const { dub_id } = req.cookies;

        // if the user is created in the last 10 minutes, consider them new
        const isNewUser =
          new Date(user.created_at) > new Date(Date.now() - 10 * 60 * 1000);

        // if the user is new and has a dub_id cookie, track the lead
        if (dub_id && isNewUser) {
          dub.track
            .lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerExternalId: user.id,
              customerName: user.user_metadata.name,
              customerEmail: user.email,
              customerAvatar: user.user_metadata.avatar_url,
            })
            .catch(console.error); // Handle any errors in tracking

          // delete the clickId cookie
          res.setHeader(
            "Set-Cookie",
            `dub_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT`,
          );
        }

        return res.redirect(`${origin}${next}`);
      }
    }

    // return the user to an error page with instructions
    return res.redirect(`${origin}/auth/auth-code-error`);
  }
  ```
</CodeGroup>

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

## Example App

To learn more about how to track leads with Supabase, check out the following example app:

<Card title="Supabase + Next.js App Router Example" icon="github" href="https://github.com/steven-tey/extrapolate/blob/main/app/api/auth/callback/route.ts">
  Check out a real-world example of this in action – Extrapolate uses Supabase
  Auth and Next.js App Router to track new user sign-ups.
</Card>

## View your conversions

Once you've completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Direct sale tracking
Source: https://dub.co/docs/conversions/sales/direct

Learn how to track sales without a prior lead event using Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

When it comes to [conversion tracking](/docs/concepts/attribution), a `sale` event happens when a user purchases your product or service. Examples include:

* Subscribing to a paid plan
* Usage expansion (upgrading from one plan to another)
* Purchasing a product from your online store

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking sales conversion events **without a prior lead event**. This is useful when you want to attribute a sale directly to a click, bypassing the lead tracking step.

## When to use direct sale tracking

Direct sale tracking is ideal for scenarios where:

* You don't need to track leads separately (e.g., one-time purchases)
* You want to attribute sales directly to clicks without intermediate steps
* You're tracking sales for users who haven't signed up or created an account

<Note>
  If you're tracking both leads and sales in your conversion funnel, refer to
  our [server-side tracking](/docs/quickstart/server) or [client-side
  tracking](/docs/quickstart/client) guides instead.
</Note>

<Warning>
  [Lead commission
  rewards](/help/article/partner-rewards#configuring-reward-types) will not be
  created when using direct sale tracking.
</Warning>

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'll need to install the Dub Analytics script and set up the necessary configuration for client-side conversion tracking:

<Steps>
  <Step title="Allowlist your site's domain">
    Then, you'll need to allowlist your site's domain to allow the client-side conversion events to be ingested by Dub.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and add your site's domain to the **Allowed Hostnames** list.

    This provides an additional layer of security by ensuring only authorized domains can track conversions using your publishable key.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    You can group your hostnames when adding them to the allow list:

    * `example.com`: Tracks traffic **only** from `example.com`.
    * `*.example.com`: Tracks traffic from **all subdomains** of `example.com`, but **not** from `example.com` itself.

    <Tip>
      When testing things out locally, you can add `localhost` to the **Allowed
      Hostnames** list temporarily. This will allow local events to be ingested by
      Dub. Don't forget to remove it once you're ready to go live!
    </Tip>
  </Step>

  <Step title="Generate your publishable key">
    Before you can track conversions on the client-side, you need to generate a [publishable key](/docs/api-reference/authentication#publishable-keys) from your Dub workspace.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>
  </Step>

  <Step title="Install Dub Analytics script">
    Next, install the Dub Analytics script on your website/web application.

    You can install the Dub Analytics script in several different ways:

    <CardGroup>
      <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

      <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

      <Card
        title="Framer"
        icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
        href="/docs/sdks/client-side/installation-guides/framer"
      />

      <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

      <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

      <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

      <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
    </CardGroup>

    <Note>
      You must configure the **publishable key** you generated in step 1 when
      installing the analytics script. Without this key, client-side conversion
      tracking will not work.
    </Note>

    <CodeGroup>
      ```html HTML theme={null}
      <script>
        !(function (c, n) {
          c[n] =
            c[n] ||
            function () {
              (c[n].q = c[n].q || []).push(arguments);
            };
          ["trackClick", "trackLead", "trackSale"].forEach(
            (t) => (c[n][t] = (...a) => c[n](t, ...a)),
          );
          var s = document.createElement("script");
          s.defer = 1;
          s.src = "https://www.dubcdn.com/analytics/script.conversion-tracking.js";
          s.setAttribute("data-publishable-key", "dub_pk_xxxxxxxx"); // Replace with your publishable key
          document.head.appendChild(s);
        })(window, "dubAnalytics");
      </script>
      ```

      ```typescript React/Next.js theme={null}
      import { Analytics as DubAnalytics } from '@dub/analytics/react';

      export default function RootLayout({
        children,
      }) {
        return (
          <html lang="en">
            <body className={inter.className}>{children}</body>
            <DubAnalytics
              ...
              publishableKey="dub_pk_xxxxxxxx" // Replace with your publishable key
            />
          </html>
        );
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Track direct sale conversions

To track a direct sale, you need to pass the `clickId` parameter along with customer information when tracking the sale event. The `clickId` can be read from the `dub_id` cookie that's automatically set when a user clicks on your Dub link.

### Track direct sales from URL query parameters

If you redirect users to a confirmation page after a successful purchase, you can track direct sales by reading query parameters from the URL and the `dub_id` cookie.

<CodeGroup>
  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useEffect } from "react";

  // Helper function to read cookie
  function getCookie(name: string) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop()?.split(";").shift();
  }

  export function OrderConfirmationPage() {
    const { trackSale } = useAnalytics();

    useEffect(() => {
      // Get query parameters from URL
      const params = new URLSearchParams(window.location.search);
      const customerId = params.get("customer_id");
      const amount = params.get("amount");
      const invoiceId = params.get("invoice_id");

      // Get click ID from cookie
      const clickId = getCookie("dub_id");

      if (customerId && amount && clickId) {
        // Track the direct sale event
        trackSale({
          eventName: "Purchase",
          customerExternalId: customerId,
          amount: parseInt(amount), // Amount in cents
          invoiceId: invoiceId || undefined,

          // Required for direct sale tracking:
          clickId: clickId,
          customerName: "John Doe", // Optional: customer name
          customerEmail: "john@example.com", // Optional: customer email
          customerAvatar: "https://example.com/avatar.jpg", // Optional: avatar URL
        });
      }
    }, [trackSale]);

    return <div>Thank you for your purchase!</div>;
  }
  ```

  ```html HTML / Other Frameworks theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Order Confirmation</title>
    </head>
    <body>
      <div>Thank you for your purchase!</div>

      <script>
        // Helper function to read cookie
        function getCookie(name) {
          const value = `; ${document.cookie}`;
          const parts = value.split(`; ${name}=`);
          if (parts.length === 2) return parts.pop().split(";").shift();
        }

        // Get query parameters from URL
        const params = new URLSearchParams(window.location.search);
        const customerId = params.get("customer_id");
        const amount = params.get("amount");
        const invoiceId = params.get("invoice_id");

        // Get click ID from cookie
        const clickId = getCookie("dub_id");

        if (customerId && amount && clickId) {
          // Track the direct sale event
          dubAnalytics.trackSale({
            eventName: "Purchase",
            customerExternalId: customerId,
            amount: parseInt(amount), // Amount in cents
            invoiceId: invoiceId || undefined,

            // Required for direct sale tracking:
            clickId: clickId,
            customerName: "John Doe", // Optional: customer name
            customerEmail: "john@example.com", // Optional: customer email
            customerAvatar: "https://example.com/avatar.jpg", // Optional: avatar URL
            currency: "usd",
            paymentProcessor: "stripe",
            metadata: { plan: "pro" },
          });
        }
      </script>
    </body>
  </html>
  ```
</CodeGroup>

### Track direct sales from form submissions

You can also track direct sales when users complete a checkout form on your website.

<CodeGroup>
  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useState } from "react";

  // Helper function to read cookie
  function getCookie(name: string) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop()?.split(";").shift();
  }

  export function CheckoutForm() {
    const { trackSale } = useAnalytics();

    const handleSubmit = (e: React.FormEvent) => {
      e.preventDefault();

      // Get click ID from cookie
      const clickId = getCookie("dub_id");

      if (clickId) {
        // Track the direct sale event
        trackSale({
          eventName: "Purchase",
          customerExternalId: "cus_RBfbD57H",
          amount: 5000, // $50.00
          invoiceId: "in_1MtHbELkdIwH",

          // Required for direct sale tracking:
          clickId: clickId,
          customerName: "John Doe",
          customerEmail: "john@example.com",
          customerAvatar: "https://example.com/avatar.jpg",
        });
      }
    };

    return (
      <form onSubmit={handleSubmit}>
        ...
        <button type="submit">Complete Purchase</button>
      </form>
    );
  }
  ```

  ```html HTML / Other Frameworks theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Checkout</title>
    </head>
    <body>
      <form id="checkoutForm">
        ...
        <button type="submit">Complete Purchase</button>
      </form>

      <script>
        // Helper function to read cookie
        function getCookie(name) {
          const value = `; ${document.cookie}`;
          const parts = value.split(`; ${name}=`);
          if (parts.length === 2) return parts.pop().split(";").shift();
        }

        document
          .getElementById("checkoutForm")
          .addEventListener("submit", function (e) {
            e.preventDefault();

            // Get click ID from cookie
            const clickId = getCookie("dub_id");

            if (clickId) {
              // Track the direct sale event
              dubAnalytics.trackSale({
                eventName: "Purchase",
                customerExternalId: "cus_RBfbD57H",
                amount: 5000, // $50.00
                invoiceId: "in_1MtHbELkdIwH",

                // Required for direct sale tracking:
                clickId: clickId,
                customerName: "John Doe",
                customerEmail: "john@example.com",
                customerAvatar: "https://example.com/avatar.jpg",
                currency: "usd",
                paymentProcessor: "stripe",
                metadata: { plan: "pro" },
              });
            }
          });
      </script>
    </body>
  </html>
  ```
</CodeGroup>

### Server-side direct sale tracking

You can also track direct sales from your backend by passing the `clickId` parameter when calling the track sale API:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Dub } from "dub";

  const dub = new Dub();

  await dub.track.sale({
    customerExternalId: "cus_RBfbD57HDzPKpduI8elr5qHA",
    amount: 5000,
    paymentProcessor: "stripe",
    eventName: "Purchase",
    invoiceId: "in_1MtHbELkdIwH",
    currency: "usd",

    // Required for direct sale tracking:
    clickId: "cm3w...", // Pass the click ID from your frontend
    customerName: "John Doe",
    customerEmail: "john@example.com",
    customerAvatar: "https://example.com/avatar.jpg",
  });
  ```

  ```python Python theme={null}
  from dub import Dub
  import os

  dub = Dub(token=os.environ['DUB_API_KEY'])

  dub.track.sale({
      'external_id': 'cus_RBfbD57HDzPKpduI8elr5qHA',
      'amount': 5000,
      'payment_processor': 'stripe',
      'event_name': 'Purchase',
      'invoice_id': 'in_1MtHbELkdIwH',
      'currency': 'usd',

      # Required for direct sale tracking:
      'click_id': 'cm3w...', # Pass the click ID from your frontend
      'customer_name': 'John Doe',
      'customer_email': 'john@example.com',
      'customer_avatar': 'https://example.com/avatar.jpg'
  })
  ```

  ```go Go theme={null}
  package main

  import (
      "context"
      dub "github.com/dubinc/dub-go"
  )

  d := dub.New(
      dub.WithSecurity(os.Getenv("DUB_API_KEY")),
  )

  _, err := d.Track.Sale(context.Background(), &operations.TrackSaleRequest{
      CustomerExternalId: "cus_RBfbD57HDzPKpduI8elr5qHA",
      Amount:             5000,
      PaymentProcessor:   "stripe",
      EventName:          "Purchase",
      InvoiceId:          "in_1MtHbELkdIwH",
      Currency:           "usd",

      // Required for direct sale tracking:
      ClickId:         "cm3w...", // Pass the click ID from your frontend
      CustomerName:    "John Doe",
      CustomerEmail:   "john@example.com",
      CustomerAvatar:  "https://example.com/avatar.jpg",
  })
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  dub = ::OpenApiSDK::Dub.new
  dub.config_security(
    ::OpenApiSDK::Shared::Security.new(
      token: ENV['DUB_API_KEY']
    )
  )

  req = ::OpenApiSDK::Operations::TrackSaleRequest.new(
    external_id: 'cus_RBfbD57HDzPKpduI8elr5qHA',
    amount: 5000,
    payment_processor: 'stripe',
    event_name: 'Purchase',
    invoice_id: 'in_1MtHbELkdIwH',
    currency: 'usd',

    # Required for direct sale tracking:
    click_id: 'cm3w...', # Pass the click ID from your frontend
    customer_name: 'John Doe',
    customer_email: 'john@example.com',
    customer_avatar: 'https://example.com/avatar.jpg'
  )

  dub.track.sale(req)
  ```

  ```php PHP theme={null}
  <?php

  require 'vendor/autoload.php';

  use Dub\Dub;
  use Dub\Models\Operations;

  $dub = Dub::builder()->setSecurity($_ENV["DUB_API_KEY"])->build();

  $request = new Operations\TrackSaleRequest();
  $request->customerExternalId = 'cus_RBfbD57HDzPKpduI8elr5qHA';
  $request->amount = 5000;
  $request->paymentProcessor = 'stripe';
  $request->eventName = 'Purchase';
  $request->invoiceId = 'in_1MtHbELkdIwH';
  $request->currency = 'usd';

  // Required for direct sale tracking:
  $request->clickId = 'cm3w...'; // Pass the click ID from your frontend
  $request->customerName = 'John Doe';
  $request->customerEmail = 'john@example.com';
  $request->customerAvatar = 'https://example.com/avatar.jpg';

  $dub->track->sale($request);
  ```
</CodeGroup>

Here are the properties you can include when sending a sale event:

| Property             | Required | Description                                                                                                                                                |
| :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
| `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
| `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
| `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
| `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
| `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
| `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
| `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
| `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
| `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
| `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |

<Note>
  **When to track sale**: Track sale events only after a user successfully
  completes a purchase or payment-related action. Ensure the event is triggered
  **only after the backend confirms the payment was successful**.
</Note>

## View your conversions

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Tracking refunds
Source: https://dub.co/docs/conversions/sales/refunds

Learn how to track refund events for your Dub partner program

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

If you're using [Dub Partners](https://dub.co/partners) for your affiliate program, you might also want to track refund events and update the associated [partner commission's status](/help/article/partner-commissions#commission-statuses) accordingly.

<Note>
  Commissions that are already paid cannot be refunded. To account for this, we
  recommend setting a [payout holding
  period](/help/article/partner-payouts#payout-holding-period) for your program
  that matches your company's refund policy.

  E.g. if you offer a 30-day refund policy, set your payout holding period to 30 days.

  If you still need to refund a commission that has already been paid, you can [create a clawback](/help/article/partner-commissions#creating-a-clawback) instead.
</Note>

## Tracking refunds with the Stripe integration

Good news: If you're using our [Stripe integration](/docs/integrations/stripe), refunds are automatically tracked without any additional setup.

<Card title="View Stripe integration ↗" href="/docs/integrations/stripe">
  <img alt="Stripe integration" />
</Card>

## Tracking refunds manually

Alternatively, if you're not using our Stripe integration and are [tracking sales manually](/docs/quickstart/server#tracking-sale-events), here's how you can track refunds:

<Steps>
  <Step title="Pass invoiceId when tracking a sale">
    When you track a sale with [`POST /track/sale`](/docs/api-reference/track/sale), make sure to pass a unique [`invoiceId`](/docs/api-reference/track/sale#body-invoice-id-one-of-0) in the request that is associated with the sale.
  </Step>

  <Step title="Get the commission">
    Then, to track a refund, find the particular commission by [filtering your
    commissions](/docs/api-reference/commissions/list) by the same `invoiceId`
    that you passed in Step 1.
  </Step>

  <Step title="Update the commission status">
    Finally, use the [`PATCH /commissions/{id}` endpoint](/docs/api-reference/commissions/update) to update the commission's status to "refunded". This will omit the commission from any pending payouts for the partner.
  </Step>
</Steps>


# Introduction
Source: https://dub.co/docs/integrations

Integrate Dub with your favorite tools and services.

Integrations allow you to extend the capabilities of Dub and seamlessly connect with third-party platforms and services. By leveraging these integrations, you can enhance your workflows, automate tasks, connect with your favorite tools, and more.

## Official integrations

These are the integrations that are officially supported and actively maintained by Dub.

<CardGroup>
  <Card
    title="Zapier"
    href="https://dub.co/integrations/zapier"
    icon={
  <img
    src="https://dubassets.com/integrations/clzlmz336000fjeqynwhfv8vo_S4yz4ak"
    alt="Zapier logo"
  />
}
  >
    Integrate Dub with Zapier
  </Card>

  <Card
    title="Make.com"
    href="https://dub.co/integrations/make"
    icon={
  <img
    src="https://dubassets.com/integrations/clzlmyxup0001jeqyaka3dvdd_GSp2tii"
    alt="Make.com logo"
  />
}
  >
    Integrate Dub with Make.com
  </Card>

  <Card
    title="Stripe"
    href="https://dub.co/integrations/stripe"
    icon={
  <img
    src="https://dubassets.com/integrations/clzra1ya60001wnj4a89zcg9h_jtyaGa7"
    alt="Stripe logo"
  />
}
  >
    Integrate Dub with Stripe
  </Card>

  <Card
    title="Shopify"
    href="https://dub.co/integrations/shopify"
    icon={
  <img
    src="https://dubassets.com/integrations/int_iWOtrZgmcyU6XDwKr4AYYqLN_jUmF77W"
    alt="Shopify logo"
  />
}
  >
    Integrate Dub with Shopify
  </Card>

  <Card
    title="Segment"
    href="https://dub.co/integrations/segment"
    icon={
  <img
    src="https://dubassets.com/integrations/int_zGnSElTzimbz20OWnXerPoKv_Noy3Xhk"
    alt="Segment logo"
  />
}
  >
    Integrate Dub with Segment
  </Card>

  <Card
    title="Wordpress"
    href="https://dub.co/integrations/wordpress"
    icon={
  <img
    src="https://dubassets.com/integrations/clzrjifgn0004tyvlu72oxcc2_GrLz146"
    alt="Wordpress logo"
  />
}
  >
    Integrate Dub with Wordpress
  </Card>

  <Card
    title="Raycast"
    href="https://dub.co/integrations/raycast"
    icon={
  <img
    src="https://dubassets.com/integrations/clzlmyzlx0005jeqy95pjrwbz_Yg767eU"
    alt="Raycast logo"
  />
}
  >
    Integrate Dub with Raycast
  </Card>

  <Card
    title="Slack"
    href="https://dub.co/integrations/slack"
    icon={
  <img
    src="https://dubassets.com/integrations/clzu59rx9000110bm5fnlzwuj_Y93aiyc"
    alt="Slack logo"
  />
}
  >
    Integrate Dub with Slack
  </Card>
</CardGroup>

## Building your own integrations

You can build your own integrations with Dub's link infrastructure using our [API](/docs/api-reference/introduction).

1. Read the documentation on how to [create links](/docs/api-reference/links/create) or [track sale conversions](/docs/api-reference/track/sale).
2. Learn how to [integrate Dub into your application](/docs/integrations/quickstart).
3. [Reach out to us](https://dub.co/contact/support) to feature your integration in the integrations marketplace.

## Integration webhooks

Dub also supports webhooks for integrations. You can learn more about them in the [webhooks](/docs/webhooks/introduction) documentation.

Here's a list of the webhooks that Dub supports:

* [`link.created`](/docs/webhooks/events/link-created) – when a new link is created
* [`link.updated`](/docs/webhooks/events/link-updated) – when a link is updated
* [`link.deleted`](/docs/webhooks/events/link-deleted) – when a link is deleted
* [`link.clicked`](/docs/webhooks/events/link-clicked) – when a link is clicked
* [`lead.created`](/docs/webhooks/events/lead-created) – when a new lead is created
* [`sale.created`](/docs/webhooks/events/sale-created) – when a new sale is created
* [`partner.enrolled`](/docs/webhooks/events/partner-enrolled) – when a new partner is enrolled in your program


# Google Tag Manager
Source: https://dub.co/docs/integrations/google-tag-manager

Learn how to track sales conversion events with Google Tag Manager and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's [Google Tag Manager integration](https://dub.co/integrations/google-tag-manager) allows you to track conversion events directly from Google Tag Manager.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Install Dub via Google Tag Manager

There are 2 steps to install the Dub Analytics script via Google Tag Manager:

1. [Add Dub Analytics script to GTM](#step-1-add-dub-analytics-script-to-gtm)
2. [Create User-Defined Variable for dub\_id Cookie](#step-2-create-user-defined-variable-for-dub_id-cookie)

### Step 1: Add Dub Analytics script to GTM

First, you'll need to add the Dub Analytics script to your website using Google Tag Manager.

In your GTM workspace, navigate to the **Tags** section and click **New** to create a new tag.

<Frame>
  <img alt="GTM New Tag" />
</Frame>

Select **Custom HTML** as the tag type and add the following code:

```html theme={null}
<script>
  (function (c, n) {
    c[n] =
      c[n] ||
      function () {
        (c[n].q = c[n].q || []).push(arguments);
      };

    var methods = ["trackClick", "trackLead", "trackSale"];
    for (var i = 0; i < methods.length; i++) {
      (function (method) {
        c[n][method] = function () {
          var args = Array.prototype.slice.call(arguments);
          args.unshift(method);
          c[n].apply(null, args);
        };
      })(methods[i]);
    }

    var s = document.createElement("script");
    s.defer = 1;
    s.src = "https://www.dubcdn.com/analytics/script.conversion-tracking.js";
    s.setAttribute("data-publishable-key", "dub_pk_xxxxxxxx"); // Replace with your publishable key
    document.head.appendChild(s);
  })(window, "dubAnalytics");
</script>
```

<Warning>
  Make sure to replace `dub_pk_xxxxxxxx` with your actual [publishable
  key](/docs/api-reference/authentication#publishable-keys) from your Dub
  workspace (under the [Analytics settings
  page](https://app.dub.co/settings/analytics))
</Warning>

Configure the tag to fire on **All Pages** by setting the trigger to **All Pages - Page View**.

Name this tag "Dub Analytics script" and save it.

### Step 2: Create User-Defined Variable for dub\_id Cookie

To read the `dub_id` cookie that Dub Analytics script sets, you'll need to create a User-Defined Variable in Google Tag Manager.

In your GTM workspace, navigate to the **Variables** section and click **New** to create a new variable.

<Frame>
  <img alt="GTM Dub ID Cookie Variable" />
</Frame>

Configure the variable with the following settings:

* **Variable Type**: Select **1st Party Cookie**
* **Cookie Name**: Enter `dub_id`
* **Variable Name**: Name it "Dub ID Cookie"

Click **Save** to create the variable.

<Note>
  This variable will automatically read the `dub_id` cookie value set by the Dub
  Analytics script. You can use this variable in your tags to pass the Dub ID
  when tracking conversion events.
</Note>

## Tracking lead events

There are two ways to track lead events with Google Tag Manager:

* [Thank You Page Tracking (Recommended)](#option-1-thank-you-page-tracking-recommended)
* [Form Submission Tracking](#option-2-form-submission-tracking)

### Option 1: Thank You Page Tracking (Recommended)

This method tracks leads when users land on a thank-you or success page after completing a form. This approach is more reliable as it's less likely to be blocked by ad blockers and provides better data accuracy.

Create a **Custom HTML** tag with the following code:

```html theme={null}
<script>
  (function () {
    // Get query parameters from URL
    var params = new URLSearchParams(window.location.search);
    var email = params.get("email");
    var name = params.get("name");

    // Get dub_id from cookie using GTM variable
    var clickId = {{Dub ID Cookie}} || "";

    // Only track the lead event if email and clickId are present
    if (email && clickId) {
      dubAnalytics.trackLead({
        eventName: "Sign Up",
        customerExternalId: email,
        customerName: name || email,
        customerEmail: email,
        clickId: clickId,
      });
    }
  })();
</script>
```

<Note>
  **Important**: Make sure to pass along the `email` and `name` query parameters
  to the thank-you page so that the lead event can be attributed to the correct
  customer.
</Note>

Configure this tag to fire on specific pages by creating a **Page View** trigger with conditions:

* Trigger Type: **Page View**
* This trigger fires on: **Some Page Views**
* Add conditions like:
  * **Page URL** contains `/thank-you`
  * Or **Page Path** equals `/success`
  * Or whatever URL pattern matches your thank-you pages

Name this tag "Dub Lead Tracking - Thank You Page" and save it.

### Option 2: Form Submission Tracking

This method tracks leads immediately when users submit forms on your website. Note that this approach may be less reliable due to ad blockers and timing issues.

Create a **Custom HTML** tag with the following code:

```html theme={null}
<script>
  (function () {
    // Get form data - customize these selectors based on your form
    var name = document.getElementById("name")
      ? document.getElementById("name").value
      : "";
    var email = document.getElementById("email")
      ? document.getElementById("email").value
      : "";

    // Get dub_id from cookie using GTM variable
    var clickId = {{Dub ID Cookie}} || "";

    // Only track the lead event if email and clickId are present
    if (email && clickId) {
      dubAnalytics.trackLead({
        eventName: "Sign Up",
        customerExternalId: email,
        customerName: name || email,
        customerEmail: email,
        clickId: clickId,
      });
    }
  })();
</script>
```

<Note>
  **Important**: You'll need to customize the DOM selectors
  (`getElementById('name')`, `getElementById('email')`) to match your actual
  form field IDs or use different methods to capture the form data based on your
  website's structure.
</Note>

Configure this tag to fire on **Form Submission** by creating a new trigger:

* Trigger Type: **Form Submission**
* This trigger fires on: **Some Forms** (or **All Forms** if you want to track all form submissions)
* Add conditions to specify which forms should trigger lead tracking

Name this tag "Dub Lead Tracking - Form Submission" and save it.

<LeadAttributes />

## Tracking sales events

There are two ways to track sales events with Google Tag Manager:

* [Order Confirmation Page Tracking (Recommended)](#option-1-order-confirmation-page-tracking-recommended)
* [Checkout Form Submission Tracking](#option-2-checkout-form-submission-tracking)

### Option 1: Order Confirmation Page Tracking (Recommended)

This method tracks sales when users land on an order confirmation or success page after completing a purchase. This approach is more reliable as it's less likely to be blocked by ad blockers and provides better data accuracy.

Create a **Custom HTML** tag with the following code:

```html theme={null}
<script>
  (function () {
    // Get query parameters from URL
    var params = new URLSearchParams(window.location.search);
    var customerId = params.get("customer_id");
    var amount = params.get("amount");
    var invoiceId = params.get("invoice_id");

    // Only track if customer ID and amount are present
    if (customerId && amount) {
      // Track the sale event
      dubAnalytics.trackSale({
        eventName: "Purchase",
        customerExternalId: customerId,
        amount: parseInt(amount), // Amount in cents
        invoiceId: invoiceId || undefined,
        currency: "usd", // Customize as needed
        paymentProcessor: "stripe", // Customize as needed
      });
    }
  })();
</script>
```

Configure this tag to fire on specific pages by creating a **Page View** trigger with conditions:

* Trigger Type: **Page View**
* This trigger fires on: **Some Page Views**
* Add conditions like:
  * **Page URL** contains `/order-confirmation`
  * Or **Page Path** equals `/checkout/success`
  * Or whatever URL pattern matches your order confirmation pages

Name this tag "Dub Sales Tracking - Order Confirmation" and save it.

### Option 2: Checkout Form Submission Tracking

This method tracks sales immediately when users complete checkout forms on your website. Note that this approach may be less reliable due to ad blockers and timing issues.

Create a **Custom HTML** tag with the following code:

```html theme={null}
<script>
  (function () {
    // Get checkout data - customize these selectors based on your form
    var customerId = document.getElementById("customer_id")
      ? document.getElementById("customer_id").value
      : "";
    var amount = document.getElementById("amount")
      ? document.getElementById("amount").value
      : "";
    var invoiceId = document.getElementById("invoice_id")
      ? document.getElementById("invoice_id").value
      : "";

    // Only track if customer ID and amount are present
    if (customerId && amount) {
      // Track the sale event
      dubAnalytics.trackSale({
        eventName: "Purchase",
        customerExternalId: customerId,
        amount: parseInt(amount), // Amount in cents
        invoiceId: invoiceId || undefined,
        currency: "usd", // Customize as needed
        paymentProcessor: "stripe", // Customize as needed
      });
    }
  })();
</script>
```

<Note>
  **Important**: You'll need to customize the DOM selectors
  (`getElementById('customer_id')`, `getElementById('amount')`, etc.) to match
  your actual checkout form field IDs or use different methods to capture the
  form data based on your website's structure.
</Note>

Configure this tag to fire on **Form Submission** by creating a new trigger:

* Trigger Type: **Form Submission**
* This trigger fires on: **Some Forms** (or **All Forms** if you want to track all form submissions)
* Add conditions to specify which forms should trigger sales tracking (e.g., checkout forms)

Name this tag "Dub Sales Tracking - Checkout Form" and save it.

Here are the properties you can include when sending a sale event:

| Property             | Required | Description                                                                                                                                                |
| :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
| `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
| `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
| `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
| `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
| `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
| `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
| `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
| `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
| `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
| `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |

## Testing your setup

To test your GTM setup, you can use the **Preview** mode in Google Tag Manager:

1. **Enable Preview Mode**: In your GTM workspace, click the **Preview** button in the top right corner
2. **Enter your website URL** and click **Connect**
3. **Test your chosen tracking method**:
   * **For Option 1 (Order Confirmation)**: Navigate to your order confirmation page with query parameters (e.g., `?customer_id=123&amount=1000&invoice_id=inv_456`)
   * **For Option 2 (Form Submission)**: Navigate to a checkout page and complete a test purchase form
4. **Check the GTM debugger** to see if your tags are firing correctly

### Verify conversion tracking

You can also verify that conversion events are being tracked by:

1. **Checking your browser's developer console** for any JavaScript errors
2. **Using the Network tab** to see if requests are being sent to Dub's analytics endpoint
3. **Viewing your Dub dashboard** to confirm that sale events are appearing in your analytics

### Common troubleshooting tips

* **Tag not firing**: Check that your triggers are configured correctly and that the conditions match your page structure
* **Form data not captured** (Option 2): Verify that your DOM selectors match your actual checkout form field IDs or names
* **Query parameters missing** (Option 1): Ensure your checkout process redirects to the confirmation page with the required query parameters
* **Amount formatting**: Ensure amounts are in cents (e.g., \$10.00 = 1000 cents)
* **Multiple events**: Make sure your tags aren't firing multiple times by checking trigger conditions
* **Duplicate tracking**: Verify you've only implemented one tracking method (Option 1 OR Option 2, not both)
* **Missing publishable key**: Ensure you've replaced the placeholder with your actual publishable key

<Warning>
  **Client-Side Tracking Limitations**:

  * **Ad blockers** – Users with ad blockers may prevent tracking scripts from loading
  * **JavaScript disabled** – Events won't be tracked if users have JavaScript disabled
  * **Network issues** – Failed network requests won't retry automatically
  * **Privacy concerns** – Some users may block client-side tracking for privacy reasons

  For more accurate conversion tracking, consider using [server-side conversion tracking](/docs/quickstart/server)
</Warning>

## View conversion results

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## Example apps

<CardGroup>
  <Card title="Dub + GTM Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/gtm">
    See the full example on GitHub.
  </Card>
</CardGroup>


# HubSpot
Source: https://dub.co/docs/integrations/hubspot

Learn how to track lead conversion events with HubSpot and Dub

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing).
</Tip>

Dub's [HubSpot integration](https://dub.co/integrations/hubspot) allows you to track conversion events directly from HubSpot.

This is useful for B2B SaaS companies that use HubSpot as their CRM and want to track meeting bookings and deal creation/closed-won events as lead and sale conversion events on Dub.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

Finally, install the [HubSpot Dub Integration](https://app.dub.co/integrations/hubspot) to your workspace.

<Frame>
  <img alt="HubSpot Dub Integration" />
</Frame>

### Integration Setup

During the installation, Dub will create 3 properties to your contact object on HubSpot:

* **Dub Id** - Unique identifier that Dub uses to track clicks
* **Dub Link** - The short link that the contact clicked to reach your site
* **Dub Partner Email** - Email address of the partner associated with the short link

<Frame>
  <img alt="HubSpot Contact Properties Created by Dub Integration" />
</Frame>

<Tip>
  If you can't see these properties in your HubSpot contact object after
  installation, something might've gone wrong with the integration setup. Please
  [contact us](https://dub.co/contact/support) for assistance.
</Tip>

### Set Your Closed Won Deal Stage ID

You can define which HubSpot deal stage should be treated as **Closed Won** for automatic sales tracking in Dub.

If you've customized your pipeline (i.e. changed or added deal stages in HubSpot), enter the stage ID corresponding to your custom **Closed Won** stage in the [HubSpot Integration Settings](https://app.dub.co/settings/integrations/hubspot).

Once set, any HubSpot deal marked as **Closed Won** will automatically be tracked in Dub as a sale conversion event, including its deal value.

<Frame>
  <img alt="Add custom deal stage ID to HubSpot Integration Settings" />
</Frame>

## Option 1: Using HubSpot Forms

[HubSpot Forms](https://www.hubspot.com/products/marketing/forms) help you capture lead information and track conversions. By integrating with Dub, you can attribute each form submission back to the specific Dub link that drove the conversion.

To make attribution work, you need to capture the `dub_id` cookie in your HubSpot form. This ensures each lead is tied back to the exact Dub link they came from.

Here's how you can set it up:

<Steps>
  <Step title="Add a hidden field to your form">
    In the HubSpot form builder, add a hidden field and map it to the **Dub Id** contact property.

    This makes sure the value captured by your script is stored on the contact record. Without mapping to a property, HubSpot won’t persist the `dub_id` value.

    <Frame>
      <img alt="Segment Dub (Actions) Mapping" />
    </Frame>
  </Step>

  <Step title="Add the script to your site">
    Finally, add the following snippet to your site. The script reads the `dub_id` cookie and, if found, automatically fills the hidden Dub Id form field with its value.

    <CodeGroup>
      ```html HTML expandable theme={null}
      <script src="https://js.hsforms.net/forms/embed/47839131.js" defer></script>

      <div
        class="hs-form-frame"
        data-region="na1"
        data-form-id="YOUR_FORM_ID"
        data-portal-id="YOUR_PORTAL_ID"
      ></div>

      <script>
        // A helper function to get the value of a cookie
        function getCookie(name) {
          const value = `; ${document.cookie}`;
          const parts = value.split(`; ${name}=`);

          if (parts.length === 2) {
            return parts.pop().split(";").shift();
          }

          return null;
        }

        // Listen for the form ready event
        window.addEventListener("hs-form-event:on-ready", (event) => {
          const clickId = getCookie("dub_id");

          if (!clickId) {
            console.debug("clickId not found. Skipping lead tracking.");
            return;
          }

          // Populate the hidden field with the dub_id
          HubSpotFormsV4.getForms()[0].setFieldValue("0-1/dub_id", clickId);
        });
      </script>
      ```
    </CodeGroup>

    When a prospect submits the form, the `dub_id` is captured and passed to HubSpot, ensuring the lead is attributed back to the original Dub link.
  </Step>
</Steps>

## Option 2: Using HubSpot Meeting Scheduler

[HubSpot's Meeting Scheduler](https://www.hubspot.com/products/sales/schedule-meeting) lets prospects book time directly with you or your team.

Since HubSpot doesn't let you add a hidden field to the scheduling form, you should handle initial lead tracking through [deferred lead tracking](/docs/conversions/leads/deferred) on the client side.

<Steps>
  <Step title="Generate your publishable key">
    Before you can track conversions on the client-side, you need to generate a publishable key from your Dub workspace.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>
  </Step>

  <Step title="Allowlist your site's domain">
    Then, you'll need to allowlist your site's domain to allow the client-side conversion events to be ingested by Dub.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and add your site's domain to the **Allowed Hostnames** list.

    This provides an additional layer of security by ensuring only authorized domains can track conversions using your publishable key.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    You can group your hostnames when adding them to the allow list:

    * `example.com`: Tracks traffic **only** from `example.com`.
    * `*.example.com`: Tracks traffic from **all subdomains** of `example.com`, but **not** from `example.com` itself.

    <Tip>
      When testing things out locally, you can add `localhost` to the **Allowed
      Hostnames** list temporarily. This will allow local events to be ingested by
      Dub. Don't forget to remove it once you're ready to go live!
    </Tip>
  </Step>

  <Step title="Add the tracking code to your site">
    Use the following code to track lead conversions when meetings are booked through the HubSpot scheduler.

    The script listens for booking events from HubSpot, extracts the customer's details (name and email), and then calls `dubAnalytics.trackLead()` with [deferred lead tracking](/docs/conversions/leads/deferred).

    This way, the lead is only tracked after the meeting is confirmed, ensuring accurate attribution.

    <CodeGroup>
      ```html HTML expandable theme={null}

      <!-- Load Dub analytics script -->
      <script>
        !(function(w, n) {
          w[n] = w[n] || function() { (w[n].q = w[n].q || []).push(arguments); };
          ["trackClick","trackLead","trackSale"].forEach(t => w[n][t] = (...a) => w[n](t, ...a));
          var s = document.createElement("script");
          s.defer = true;
          s.src = "https://www.dubcdn.com/analytics/script.conversion-tracking.js";
          s.setAttribute("data-publishable-key", "YOUR_PUBLISHABLE_KEY");
          s.setAttribute("data-domains", '{"refer":"YOUR_SHORT_DOMAIN"}');
          document.head.appendChild(s);
        })(window, "dubAnalytics");
      </script>

      <div
        class="meetings-iframe-container"
        data-src="https://meetings.hubspot.com/YOUR_USERNAME?embed=true"
      ></div>

      <script
        type="text/javascript"
        src="https://static.hsappstatic.net/MeetingsEmbed/ex/MeetingsEmbedCode.js"
      ></script>

      <script>
        // Listen for the message event
        window.addEventListener("message", function (event) {
          // Check if the message is from the scheduling widget
          if (event.origin === "https://meetings.hubspot.com") {
            // Get the data from the event
            const data = event.data;

            if (data.meetingBookSucceeded) {
              // Get the scheduled contact
              const contact =
                data.meetingsPayload.bookingResponse.postResponse.contact;

              if (!contact) {
                console.debug("contact not found. Skipping lead tracking.");
                return;
              }

              // Track the lead with the scheduled contact
              const customerName = [contact.firstName, contact.lastName]
                .filter(Boolean)
                .join(" ");

              dubAnalytics.trackLead({
                mode: "deferred",
                eventName: "Meeting scheduled",
                customerExternalId: contact.email,
                customerName: customerName,
                customerEmail: contact.email,
              });
            }
          }
        });
      </script>
      ```
    </CodeGroup>
  </Step>
</Steps>

## Tracking conversion events

After a prospect attends your demo call, you'll typically create a deal in HubSpot to track the sales opportunity.

Dub's HubSpot integration automatically sets up webhooks to track both deal creation and deal closure events, providing complete visibility into your sales funnel.

### When a deal is created (lead event)

When you create a deal in HubSpot for a contact who came through your Dub links, the integration automatically tracks this as a [lead event](/docs/quickstart/server#tracking-lead-events) in Dub.

### When a deal is closed (sale event)

When a deal moves to a **Closed Won** status in HubSpot, the integration automatically [tracks a sale event](/docs/quickstart/server#tracking-sale-events) in Dub using the deal amount as the sale value.

If a deal is not being tracked as a sale in Dub, make sure you've set the correct Closed Won Deal Stage ID in your [HubSpot Integration Settings](#set-your-closed-won-deal-stage-id).

## Example apps

<CardGroup>
  <Card title="Dub + HubSpot Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/hubspot">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Build your own integration
Source: https://dub.co/docs/integrations/quickstart

Learn how to authenticate users with OAuth 2.0. for your Dub integration.

Integrations allow you to extend the capabilities of Dub and seamlessly connect with third-party platforms and services.

You can build your own integrations with Dub using our [API](/docs/api-reference/introduction).

1. Read the documentation on how to [create links](/docs/api-reference/links/create) or [track sale conversions](/docs/api-reference/track/sale).
2. Learn how to [integrate Dub into your application](/docs/integrations/quickstart).
3. [Reach out to us](https://dub.co/contact/support) to feature your integration in the integrations marketplace.

In this guide, you will learn how to create and manage integrations on Dub, allowing you to incorporate Dub's link attribution platform into your application.

## Integrating via OAuth 2.0 (recommended)

Dub supports OAuth 2.0 authentication, which is **recommended** if you build integrations extending Dub's functionality.

We recommend you use a OAuth client library to integrate the OAuth flow. You can find recommended libraries in a variety of programming languages [here](https://oauth.net/code/).

<Info>
  OAuth endpoints (`/oauth/authorize`, `/oauth/token`, `/oauth/userinfo`) are
  not available in the Dub SDKs. You'll need to call these endpoints directly
  using HTTP requests or an OAuth client library.
</Info>

### Set up OAuth 2.0

Here is a step-by-step guide on how to set up OAuth 2.0 authentication with Dub.

<Steps>
  <Step title="Create an OAuth2 application in Dub">
    * Go to the [OAuth Apps tab](https://app.dub.co/settings/oauth-apps) in your workspace.
    * Click on **Create OAuth App**.
    * Fill in the required fields to create an OAuth2 application.
  </Step>

  <Step title="Redirect users to authorization URL">
    When you want to authenticate a user, you need to redirect them to the Dub OAuth authorization URL.

    ```
    GET https://app.dub.co/oauth/authorize
    ```

    Parameters:

    | Property                | Description                                                                                                                       |
    | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
    | `client_id`             | The client ID of your OAuth application.                                                                                          |
    | `redirect_uri`          | The URL to redirect the user to after they authorize the application. Make sure this URL is registered in your OAuth application. |
    | `response_type`         | Expected response type. It should be `code`.                                                                                      |
    | `scope`                 | A space separated list of scopes that you want to request access to. Read more about scopes [here](#scopes).                      |
    | `state`                 | The state parameter to prevent against CSRF attacks. Read more about it [here](https://auth0.com/docs/protocols/state-parameters) |
    | `code_challenge`        | Required for PKCE. The code challenge generated from the `code_verifier`.                                                         |
    | `code_challenge_method` | Required for PKCE. The method used to generate the code challenge. It should be `S256`.                                           |

    <Info>
      PKCE (Proof Key for Code Exchange) is enabled by default and recommended for all applications. If you include `code_challenge` and `code_challenge_method` in the authorization request, you must also include the `code_verifier` when exchanging the code for an access token in Step 3.
    </Info>

    An example URL would look like this:

    ```
    GET https://app.dub.co/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=SOME_SCOPE&state=SOME_STATE&code_challenge=YOUR_CODE_CHALLENGE&code_challenge_method=S256
    ```

    <Frame>
      <img alt="OAuth consent screen" />
    </Frame>
  </Step>

  <Step title="Exchange code for an access token">
    The `code` parameter is returned in the query string when the user is redirected back to your application. You can exchange this code for an access token by making a POST request to the Dub OAuth token URL.

    ```
    POST https://api.dub.co/oauth/token
    ```

    <Warning>
      The `Content-Type` header should be set to `application/x-www-form-urlencoded`.
    </Warning>

    <Tabs>
      <Tab title="With PKCE (recommended)">
        We recommend using the [PKCE](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce) flow for all applications, especially native desktop or mobile applications and single-page apps (SPAs) where the `client_secret` cannot be hidden.

        With PKCE, the `client_secret` is **never sent to the authorization server**, preventing the `client_secret` from being leaked from the client application.

        ```js theme={null}
        await fetch("https://api.dub.co/oauth/token", {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
          },
          body: new URLSearchParams({
            code: "YOUR_AUTHORIZATION_CODE",
            client_id: "YOUR_CLIENT_ID",
            code_verifier: "YOUR_CODE_VERIFIER",
            redirect_uri: "YOUR_REDIRECT_URI",
            grant_type: "authorization_code",
          }),
        });
        ```

        Parameters:

        | Property        | Description                                                                  |
        | --------------- | ---------------------------------------------------------------------------- |
        | `code`          | The code you received when the user was redirected back to your application. |
        | `client_id`     | The client ID of your OAuth application.                                     |
        | `code_verifier` | The original code verifier used to generate the `code_challenge` in Step 2.  |
        | `redirect_uri`  | The same redirect URI you used in the authorization URL.                     |
        | `grant_type`    | The grant type. It should be `authorization_code`.                           |

        For example, the [Dub Raycast extension](https://github.com/raycast/extensions/tree/main/extensions/dub) uses PKCE to authenticate users.
      </Tab>

      <Tab title="With client secret">
        If you're building a server-side application where the `client_secret` can be securely stored, you can use the standard flow.

        ```js theme={null}
        await fetch("https://api.dub.co/oauth/token", {
          method: "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
          },
          body: new URLSearchParams({
            code: "YOUR_AUTHORIZATION_CODE",
            client_id: "YOUR_CLIENT_ID",
            client_secret: "YOUR_CLIENT_SECRET",
            redirect_uri: "YOUR_REDIRECT_URI",
            grant_type: "authorization_code",
          }),
        });
        ```

        Parameters:

        | Property        | Description                                                                  |
        | --------------- | ---------------------------------------------------------------------------- |
        | `code`          | The code you received when the user was redirected back to your application. |
        | `client_id`     | The client ID of your OAuth application.                                     |
        | `client_secret` | The client secret of your OAuth application.                                 |
        | `redirect_uri`  | The same redirect URI you used in the authorization URL.                     |
        | `grant_type`    | The grant type. It should be `authorization_code`.                           |
      </Tab>
    </Tabs>

    Response:

    After a successful request, you will receive a JSON response with the access token.

    ```json theme={null}
    {
      "access_token": "dub_access_token_ae8ebf6f97e6200d886ef48a5...",
      "refresh_token": "7f5acfbe14bca0a20fe6e430ddb7bb494eed160bd...",
      "token_type": "Bearer",
      "expires_in": 7200,
      "scope": "links.write tags.write domains.read"
    }
    ```
  </Step>

  <Step title="Retrieve user and workspace info">
    After obtaining an access token, you can retrieve information about the authenticated user and their workspace by calling the userinfo endpoint:

    ```
    GET https://api.dub.co/oauth/userinfo
    ```

    Here's an example using curl:

    ```bash theme={null}
    curl --request GET \
      --url https://api.dub.co/oauth/userinfo \
      --header 'Authorization: Bearer <ACCESS_TOKEN>'
    ```

    Response:

    ```json theme={null}
    {
      "id": "user_id",
      "name": "John Doe",
      "image": "https://example.com/avatar.png",
      "workspace": {
        "id": "ws_abc123",
        "slug": "acme",
        "name": "Acme Inc",
        "logo": "https://example.com/logo.png"
      }
    }
    ```
  </Step>

  <Step title="Make an API request with the access token">
    Once you have obtained a valid access token, you can use it to make requests to the Dub API.

    You can initialize [Dub SDK](/docs/sdks/overview) with the access token.

    Here is an example of how you can create a link using the [Dub TypeScript SDK](/docs/sdks/typescript):

    ```javascript theme={null}
    import { Dub } from "dub";

    const dub = new Dub({
      token: <ACCESS_TOKEN>,
    });

    const link = await dub.links.create({
      url: "https://google.com",
    });
    ```

    Or pass the access token in the header: `Authorization: Bearer <ACCESS_TOKEN>`

    ```shell theme={null}
      curl --request POST \
      --url https://api.dub.co/links \
      --header 'Authorization: Bearer <ACCESS_TOKEN>' \
      --header 'Content-Type: application/json'
    ```
  </Step>

  <Step title="Refresh the access token">
    Dub access tokens are short-lived, depending on the `expires_in` value (the default value is **7,200 seconds**, or **2 hours**). Dub will respond with `401 Unauthorized` if you try to use an expired access token.

    To refresh the access token, you need to make a POST request to the Dub OAuth token URL with the `refresh_token` you obtained when exchanging the code for an `access_token`.

    ```
    POST https://api.dub.co/oauth/token
    ```

    <Warning>
      The `Content-Type` header should be set to `application/x-www-form-urlencoded`.
    </Warning>

    Parameters:

    | Property        | Description                                                                  |
    | --------------- | ---------------------------------------------------------------------------- |
    | `client_id`     | The client ID of your OAuth application.                                     |
    | `client_secret` | The client secret of your OAuth application.                                 |
    | `grant_type`    | The grant type. It should be `refresh_token`.                                |
    | `refresh_token` | The refresh token you received when exchanging the code for an access token. |

    Response:

    After a successful request, you will receive a JSON response with the new access token.

    ```json theme={null}
    {
      "access_token": "dub_access_token_ae8ebf6f97e6200d886ef48a5...",
      "refresh_token": "7f5acfbe14bca0a20fe6e430ddb7bb494eed160bd...",
      "token_type": "Bearer",
      "expires_in": 7200,
      "scope": "links.write tags.write domains.read"
    }
    ```

    This will invalidate the old access token and refresh token.
  </Step>
</Steps>

### Scopes

You can request access to specific scopes when redirecting users to the Dub OAuth authorization URL. Scopes are permissions that the user needs to grant to your application.

Dub supports the following scopes for OAuth 2.0:

| Scope            | Description                                                         |
| ---------------- | ------------------------------------------------------------------- |
| `links.read`     | Read access to links.                                               |
| `links.write`    | Write access to links.                                              |
| `tags.read`      | Read access to tags.                                                |
| `tags.write`     | Write access to tags.                                               |
| `analytics.read` | Read access to analytics.                                           |
| `domains.read`   | Read access to domains.                                             |
| `domains.write`  | Write access to domains.                                            |
| `folders.read`   | Read access to folders.                                             |
| `folders.write`  | Write access to folders.                                            |
| `user.read`      | Read access to user information. This scope is included by default. |

### Examples

<CardGroup>
  <Card title="OAuth 2.0 Example" icon="github" href="https://github.com/dubinc/examples/tree/main/oauth">
    See the full example on GitHub.
  </Card>
</CardGroup>

## Integrating via API keys (not recommended)

Dub also supports API key authentication; however, it is **not recommended** for building integrations. It should only be used for internal integrations or personal projects that do not require user consent.

Learn more about [API Keys](/docs/api-reference/authentication#api-keys).


# Segment
Source: https://dub.co/docs/integrations/segment

Learn how to track sale conversion events with Segment and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's [Segment integration](https://dub.co/integrations/segment) provides a bi-directional sync between your Segment workspace and Dub. There are 2 parts to the integration:

* [Dub (Actions) Destination](https://www.twilio.com/docs/segment/connections/destinations/catalog/actions-dub) – Tracking conversion events on Dub directly from Segment
* [Dub Source](https://www.twilio.com/docs/segment/connections/sources/catalog/cloud-apps/dub) – Sending customer events from Dub to Segment

In this guide, we will be focusing on using the [Dub (Actions) Destination](https://www.twilio.com/docs/segment/connections/destinations/catalog/actions-dub) to track conversion events directly from Segment.

## Prerequisites

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

Then, you'd want to install the Dub Analytics script to your website to track conversion events.

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Configure Segment Action

Next, configure [Segment Dub (Actions)](https://app.segment.com/goto-my-workspace/destinations/catalog/actions-dub) to track sales conversion events.

<Steps>
  <Step title="Add Dub (Actions) destination">
    Head to [Segment Dub (Actions)](https://app.segment.com/goto-my-workspace/destinations/catalog/actions-dub) and add the destination to your Segment workspace.

    <Frame>
      <img alt="Segment Dub (Actions) destination" />
    </Frame>
  </Step>

  <Step title="Configure Dub API Key">
    In the Dub (Actions) destination settings, fill out the following fields:

    * **Name:** Enter a name to help you identify this destination in Segment.
    * **API Key:** Enter your [Dub API key](/docs/api-reference/authentication#api-keys). You can find this in the [Dub Dashboard](https://app.dub.co/settings/tokens).
    * **Enable Destination:** Toggle this on to allow Segment to send data to Dub.

    Once completed, click **Save Changes**.

    <Frame>
      <img alt="Segment Dub (Actions) Basic Settings" />
    </Frame>
  </Step>

  <Step title="Add Mapping">
    Next, depending on the event you want to track, choose the corresponding action from the list of available actions:

    | Event to track | Default event name | Example                        |
    | -------------- | ------------------ | ------------------------------ |
    | Lead           | Sign Up            | A user signs up for an account |
    | Sale           | Order Completed    | A user purchases a product     |

    <Frame>
      <img alt="Segment Dub (Actions) Mapping" />
    </Frame>

    Below the selected action, you’ll see the mapping for that action.

    <Frame>
      <img alt="Segment Dub (Actions) Mapping" />
    </Frame>

    You can customize the trigger and mapping to fit the specific needs of your application.

    Finally, click **Next** and then **Save and enable** to add the mapping to the destination.
  </Step>

  <Step title="Send conversion events to Dub">
    On the server side, you’ll use the `@segment/analytics-node` SDK to send conversion events to Segment.

    Make sure to include relevant properties in the payload:

    <CodeGroup>
      ```tsx Track a lead theme={null}
      import { Analytics } from "@segment/analytics-node";

      const segment = new Analytics({
        writeKey: "<YOUR_SEGMENT_WRITE_KEY>",
      });

      const cookieStore = await cookies();
      const clickId = cookieStore.get("dub_id")?.value;

      segment.track({
        userId: id,
        event: "Sign Up",
        context: {
          traits: {
            name,
            email,
            avatar,
            clickId,
          },
        },
        integrations: {
          All: true,
        },
      });
      ```

      ```tsx Track a sale theme={null}
      import { Analytics } from "@segment/analytics-node";

      const segment = new Analytics({
        writeKey: "<YOUR_SEGMENT_WRITE_KEY>",
      });

      segment.track({
        userId: id,
        event: "Order Completed",
        properties: {
          payment_processor: "stripe",
          order_id: "ORD_123",
          currency: "USD",
          revenue: 1000,
        },
        integrations: {
          All: true,
        },
      });
      ```
    </CodeGroup>

    Once the event is tracked, Segment will forward the conversion data to Dub based on the mappings you’ve configured.
  </Step>
</Steps>

## Supported attributes

<Tabs>
  <Tab title="Lead event">
    Here are the properties you can include when sending a lead event:

    | Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
    | :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
    | `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
    | `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
    | `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
    | `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
    | `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
    | `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
    | `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |
  </Tab>

  <Tab title="Sale event">
    Here are the properties you can include when sending a sale event:

    | Property             | Required | Description                                                                                                                                                |
    | :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
    | `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
    | `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
    | `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
    | `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
    | `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
    | `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
    | `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
    | `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
    | `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
    | `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |
  </Tab>
</Tabs>

## View conversion results

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## Example apps

<Card title="Segment + Next.js App Router Example" icon="github" href="https://github.com/dubinc/examples/blob/main/conversions/segment/actions/track-sale.ts">
  Next.js app using Segment to track sales.
</Card>


# Shopify
Source: https://dub.co/docs/integrations/shopify

Learn how to track sale conversion events with Shopify and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's best-in-class [Shopify integration](https://dub.co/integrations/shopify) listens to orders from Shopify and tracks them as sales on Dub.

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking sale events from Shopify by leveraging Dub's Shopify integration.

<iframe title="Loom video player" />

## Step 1: Enable conversion tracking for your links

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Step 2: Install the Dub Shopify app

<Steps>
  <Step title="Install the Shopify app">
    Install the [Dub Shopify App](https://d.to/shopify/app) from the App Store.

    <Frame>
      <img alt="The connection status in the Dub app" />
    </Frame>
  </Step>

  <Step title="Connect Shopify to your Dub workspace">
    After installation, you will be prompted to link one of your Dub workspaces to
    the app. Select **Connect** to establish a connection between your
    Shopify store and your Dub workspace.

    You'll be redirected back to your Shopify store after this step and you'll see a list of the links in your Dub workspace:

    <Frame>
      <img alt="The list of links in your Dub workspace" />
    </Frame>

    With the Shopify app, you can also create [conversion-enabled links](/docs/conversions//quickstart#step-1-enable-conversion-tracking-for-your-links) directly from your Shopify store:

    <Frame>
      <img alt="Create a conversion-enabled link from your Shopify store" />
    </Frame>

    If you want a more powerful link builder, you can also use the [Dub Link Builder](/help/article/dub-link-builder) to create conversion-enabled links.
  </Step>

  <Step title="Activate Dub Analytics Script">
    After installing the Dub Shopify app, the Dub Analytics script is added as an app embed. However, it needs to be activated manually to ensure it is included in your current theme.

    To activate the Dub Analytics script, follow these steps:

    1. Navigate to your Shopify admin panel.
    2. Go to **Online Store** > **Themes**.
    3. Click on **Customize** for your current theme.
    4. In the theme editor, select the **App embeds** tab.
    5. Locate the **Analytics Script** for the Dub Shopify app and toggle it to activate.

    <Frame>
      <img alt="Enable the Dub Analytics script in your Shopify theme" />
    </Frame>
  </Step>
</Steps>

<Tip>
  Dub’s Shopify integration will automatically forward the following events to Dub:

  * `orders/paid`: This event is triggered when a customer completes a purchase on your Shopify store. It is utilized to track sales that originate from Dub links.
  * `app/uninstalled`: This event occurs when the app is uninstalled from a store. It is used to remove the integration from your Dub workspace.

  In addition to the above, we also subscribe to the mandatory compliance webhook topics that are required by Shopify.
</Tip>

## Step 3: View conversion results

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## Currency conversion support

For simplicity, Dub records all sales in the native currency of the Shopify store. For example, if you're using USD for your Shopify store, Dub will record all sales in USD – even if your customers are paying in a different currency.

```json orders/paid theme={null}
// Shopify orders/paid event payload
// @see: https://shopify.dev/docs/api/webhooks?reference=toml#list-of-topics-orders/paid
{
  ...
  "current_subtotal_price_set": {
    "shop_money": {
      "amount": "398.00", // this is the amount that Dub will record
      "currency_code": "USD" // this is the currency of your Shopify store
    },
    "presentment_money": {
      "amount": "572.25",
      "currency_code": "CAD"
    }
  },
  ...
}
```


# Stripe
Source: https://dub.co/docs/integrations/stripe

Learn how to track sale conversion events with Stripe and Dub

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's best-in-class [Stripe integration](https://dub.co/integrations/stripe) listens to payment events on Stripe and tracks them as sales on Dub.

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

Here are the events that Dub listens to:

* Recurring subscriptions
* One-time payments
* [Free trials](#tracking-free-trials)
* Refunds (for [voiding partner commissions](/help/article/partner-commissions#commission-statuses))
* Cancellations/churn
* Usage expansion

In this guide, we'll learn how to install and set up the Dub Stripe integration for tracking sale conversion events.

## Installing the Dub Stripe integration

<Steps>
  <Step title="Find Dub on the Stripe App Marketplace">
    Navigate to the [Dub Stripe Integration](https://d.to/stripe/app) on the Stripe App Marketplace.

    <Frame>
      <img alt="The Dub integration page on the Stripe App Marketplace" />
    </Frame>
  </Step>

  <Step title="Install the Stripe app">
    On the top right, click on **Install app** to install the Dub app on your Stripe account.

    <Frame>
      <img alt="The Stripe integration installation flow" />
    </Frame>

    <Tip>
      Alternatively, you can also install the Stripe app in a [Stripe
      sandbox](https://docs.stripe.com/sandboxes) first to test your end-to-end flow
      without involving real money.
    </Tip>

    Once the app is installed, click on **Continue to app settings** to finish the installation.

    <Frame>
      <img alt="Continue to app settings" />
    </Frame>
  </Step>

  <Step title="Connect Stripe to your Dub workspace">
    In the app settings page, click on **Connect workspace** to connect your Stripe account with your Dub workspace.

    <Frame>
      <img alt="Connect Stripe to Dub" />
    </Frame>

    This will redirect you to the [Dub OAuth flow](/docs/integrations/quickstart), where you can select the Dub workspace you want to connect to your Stripe account.

    <Frame>
      <img alt="Select the Dub workspace you want to connect to your Stripe account" />
    </Frame>

    Once you click on **Authorize**, you will be redirected back to the Dub app settings page on Stripe, where you should see that the integration is now installed.

    <Frame>
      <img alt="The Stripe integration is now installed" />
    </Frame>
  </Step>
</Steps>

Once the integration is installed, Dub will automatically listen to the following events on Stripe and track them as sales on Dub:

* `customer.created`: When a new customer is created
* `customer.updated`: When a customer is updated
* `checkout.session.completed`: When a customer completes a checkout session
* `invoice.paid`: When an invoice is paid (for tracking recurring subscriptions)
* `charge.refunded`: When a charge is refunded (for [voiding partner commissions](/help/article/partner-commissions#commission-statuses))

## Tracking sales with the Dub Stripe integration

Depending on your setup, there are a few ways you can track sales with the Dub Stripe integration.

* [Option 1: Using Stripe Payment Links](#option-1-using-stripe-payment-links)
* [Option 2: Using Stripe Checkout (recommended)](#option-2-using-stripe-checkout-recommended)
* [Option 3: Using Stripe Customers](#option-3-using-stripe-customers)

### Option 1: Using Stripe Payment Links

<Tip>
  For this option to work, you need to [install the Dub Stripe
  integration](#installing-the-dub-stripe-integration) and [enable conversion
  tracking for your
  links](/docs/quickstart/server#step-1-enable-conversion-tracking-for-your-links)
  first.
</Tip>

<Note>
  When using Stripe Payment Links, lead and sale events are tracked but lead
  webhooks and [lead
  rewards](/help/article/partner-rewards#configuring-reward-types) will not be
  generated. [Sale
  rewards](/help/article/partner-rewards#configuring-reward-types) (both
  recurring and one time rewards) will still be generated as usual.
</Note>

If you're using [Stripe Payment Links](https://docs.stripe.com/payment-links), simply add a `?dub_client_reference_id=1` query parameter to your Stripe Payment Link when shortening it on Dub.

Then, when a user clicks on the shortened link, Dub will automatically append the unique click ID as the `client_reference_id` [query parameter](https://docs.stripe.com/payment-links/url-parameters) to the payment link.

<Frame>
  <img alt="Stripe payment link with Dub click ID" />
</Frame>

Finally, when the user completes the checkout flow, Dub will automatically [track the sale event](/docs/api-reference/track/sale) and [update the customer's `customerExternalId`](/docs/api-reference/customers/update) with their Stripe customer ID for future reference.

Alternatively, if you have a marketing site that you're redirecting your users to first, you can do this instead:

1. [Install the Dub Analytics script](/docs/sdks/client-side/introduction), which automatically detects the `dub_id` in the URL and stores it as a first-party cookie on your site.
2. Then, retrieve and append the `dub_id` value as the `client_reference_id` parameter to the payment links on your pricing page / CTA button (prefixed with `dub_id_`).

   ```
   https://buy.stripe.com/xxxxxx?client_reference_id=dub_id_xxxxxxxxxxxxxx
   ```

<AccordionGroup>
  <Accordion title="What if I'm using Stripe Pricing Tables?">
    If you're using [Stripe Pricing Tables](https://docs.stripe.com/payments/checkout/pricing-table) – you'd want to pass the Dub click ID as a [`client-reference-id` attribute](https://docs.stripe.com/payments/checkout/pricing-table#handle-fulfillment-with-the-stripe-api) instead:

    <CodeGroup>
      ```html HTML theme={null}
      <body>
        <h1>We offer plans that help any business!</h1>
        <!-- Paste your embed code script here. -->
        <script async src="https://js.stripe.com/v3/pricing-table.js"></script>
        <stripe-pricing-table
          pricing-table-id="{{PRICING_TABLE_ID}}"
          publishable-key="pk_test_51ODHJvFacAXKeDpJsgWLQJSzBIDtCUFN6MoB4IIXKJDfWdFmiEO4JuvAU1A0Y2Ri4m4q1egIfwYy3s72cUBRCwXC00GQhEZuXa"
          client-reference-id="dub_id_xxxxxxxxxxxxxx"
        >
        </stripe-pricing-table>
      </body>
      ```

      ```jsx React theme={null}
      import * as React from "react";

      function PricingPage() {
        // Paste the stripe-pricing-table snippet in your React component
        return (
          <stripe-pricing-table
            pricing-table-id="'{{PRICING_TABLE_ID}}'"
            publishable-key="pk_test_51ODHJvFacAXKeDpJsgWLQJSzBIDtCUFN6MoB4IIXKJDfWdFmiEO4JuvAU1A0Y2Ri4m4q1egIfwYy3s72cUBRCwXC00GQhEZuXa"
            client-reference-id="dub_id_xxxxxxxxxxxxxx"
          ></stripe-pricing-table>
        );
      }

      export default PricingPage;
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="What if I'm using Stripe's Checkout Sessions API?">
    If you're using Stripe's [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions/object) for a recurring subscription service, you might want to check out our [Stripe Checkout option](#option-2-using-stripe-checkout-recommended) instead.

    If your setup doesn't involve a lead/signup event and goes straight to the Stripe checkout flow (e.g. for one-time purchases), you can simply pass the Dub click ID (prefixed with `dub_id_`) as the [`client_reference_id` parameter](https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-client_reference_id) to enable conversion tracking with Dub.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const session = await stripe.checkout.sessions.create({
        success_url: "https://example.com/success",
        line_items: [
          {
            price: "price_xxxxxxxxxxxxxxxx",
            quantity: 2,
          },
        ],
        mode: "payment",
        client_reference_id: "dub_id_xxxxxxxxxxxxxx",
      });
      ```

      ```python Python theme={null}
        stripe.checkout.Session.create(
          success_url="https://example.com/success",
          line_items=[{"price": "price_xxxxxxxxxxxxxxxx", "quantity": 2}],
          mode="payment",
          client_reference_id="dub_id_xxxxxxxxxxxxxx",
        )
      ```

      ```go Go theme={null}
        params := &stripe.CheckoutSessionParams{
          SuccessURL: stripe.String("https://example.com/success"),
          LineItems: []*stripe.CheckoutSessionLineItemParams{
            &stripe.CheckoutSessionLineItemParams{
              Price: stripe.String("price_xxxxxxxxxxxxxxxx"),
              Quantity: stripe.Int64(2),
            },
          },
          Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
          ClientReferenceID: stripe.String("dub_id_xxxxxxxxxxxxxx"),
        };
        result, err := session.New(params);
      ```

      ```php PHP theme={null}
        $stripe->checkout->sessions->create([
          'success_url' => 'https://example.com/success',
          'line_items' => [
            [
              'price' => 'price_xxxxxxxxxxxxxxxx',
              'quantity' => 2,
            ],
          ],
          'mode' => 'payment',
          'client_reference_id' => "dub_id_xxxxxxxxxxxxxx",
        ]);
      ```

      ```ruby Ruby theme={null}
        Stripe::Checkout::Session.create({
          success_url: 'https://example.com/success',
          line_items: [
            {
              price: 'price_xxxxxxxxxxxxxxxx',
              quantity: 2,
            },
          ],
          mode: 'payment',
          client_reference_id: "dub_id_xxxxxxxxxxxxxx",
        })
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

### Option 2: Using Stripe Checkout (recommended)

If you have a custom checkout flow that uses Stripe's `checkout.sessions.create` API, you'd want to associate the [Stripe customer object](https://docs.stripe.com/api/customers/object) with the user's unique ID in your database (which we tracked in the [lead conversion tracking step](/docs/quickstart/server#tracking-lead-events)).

This will allow Dub to automatically listen for purchase events from Stripe and associate them with the original click event (and by extension, the link that the user came from).

<Accordion title="How does this work?">
  Remember in the [lead conversion tracking guide](/docs/quickstart/server#tracking-lead-events), we passed the user's unique user ID along with the click event ID in the `dub.track.lead` call?

  ```javascript Node.js theme={null}
  await dub.track.lead({
    clickId,
    eventName: "Sign Up",
    customerExternalId: user.id, // the unique user ID of the customer in your database
    customerName: user.name,
    customerEmail: user.email,
    customerAvatar: user.image,
  });
  ```

  Under the hood, Dub records the user as a customer and associates them with the click event that they came from.

  Then, when the user makes a purchase, Dub will automatically associate the checkout session details (invoice amount, currency, etc.) with the customer – and by extension, the original click event.
</Accordion>

First, you'll need to complete the following prerequisites:

1. [Install the Dub Stripe integration](#installing-the-dub-stripe-integration)
2. [Install the Dub Analytics script](/docs/sdks/client-side/introduction)
3. [Install the Dub server-side SDK](/docs/sdks/overview#server-side-sdks)

Then, when you [create a checkout session](https://docs.stripe.com/api/checkout/sessions/create), pass your customer's unique user ID in your database as the `dubCustomerExternalId` value in the `metadata` field.

```javascript Node.js theme={null}
import { stripe } from "@/lib/stripe";

const user = {
  id: "user_123",
  email: "user@example.com",
  teamId: "team_xxxxxxxxx",
};

const priceId = "price_xxxxxxxxx";

const stripeSession = await stripe.checkout.sessions.create({
  customer_email: user.email,
  success_url: "https://app.domain.com/success",
  line_items: [{ price: priceId, quantity: 1 }],
  mode: "subscription",
  client_reference_id: user.teamId,
  metadata: {
    dubCustomerExternalId: user.id, // the unique user ID of the customer in your database
  },
});
```

This way, when the customer completes their checkout session, Dub will automatically associate the checkout session details (invoice amount, currency, etc.) with the customer – and by extension, the original click event.

<Warning>
  If you're using [guest checkout](https://docs.stripe.com/payments/checkout/guest-customers) (e.g. with `mode: "payment"`), the `customer` field in the `checkout.session.completed` webhook event will be `null`, and sales won't be tracked on Dub.

  To fix this, set `customer_creation` to `always` when [creating your checkout session](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer_creation):

  ```javascript Node.js theme={null}
  const stripeSession = await stripe.checkout.sessions.create({
    // ... other options
    customer_creation: "always", // ensures a Stripe customer is created
  });
  ```
</Warning>

### Option 3: Using Stripe Customers

Alternatively, if you don't use Stripe's [checkout session creation flow](#option-2-using-stripe-checkout-recommended), you can also pass the user ID and the click event ID (`dub_id`) in the [Stripe customer creation flow](https://docs.stripe.com/api/customers/create).

First, you'll need to complete the following prerequisites:

1. [Install the Dub Stripe integration](#installing-the-dub-stripe-integration)
2. [Enable conversion tracking for your links](/docs/quickstart/server#step-1-enable-conversion-tracking-for-your-links)
3. [Install the Dub Analytics script](/docs/sdks/client-side/introduction)

Then, when you [create a Stripe customer](https://docs.stripe.com/api/customers/create), pass the user's unique user ID in your database as the `dubCustomerExternalId` value in the `metadata` field.

```javascript Node.js theme={null}
import { stripe } from "@/lib/stripe";

const user = {
  id: "user_123",
  email: "user@example.com",
  teamId: "team_xxxxxxxxx",
};

const dub_id = req.headers.get("dub_id");

await stripe.customers.create({
  email: user.email,
  name: user.name,
  metadata: {
    dubCustomerExternalId: user.id,
    dubClickId: dub_id,
  },
});
```

Alternatively, you can also pass the `dubCustomerExternalId` and `dubClickId` values in the `metadata` field of the [Stripe customer update flow](https://docs.stripe.com/api/customers/update):

```javascript Node.js theme={null}
import { stripe } from "@/lib/stripe";

const user = {
  id: "user_123",
  email: "user@example.com",
  teamId: "team_xxxxxxxxx",
};

const dub_id = req.headers.get("dub_id");

await stripe.customers.update(user.id, {
  metadata: {
    dubCustomerExternalId: user.id,
    dubClickId: dub_id,
  },
});
```

This way, when the customer makes a purchase, Dub will automatically associate the purchase details (invoice amount, currency, etc.) with the original click event.

<Note>
  When using Stripe Customers, lead and sale events are tracked but [lead
  rewards](/help/article/partner-rewards#configuring-reward-types) will not be
  generated. [Sale
  rewards](/help/article/partner-rewards#configuring-reward-types) (both
  recurring and one-time rewards) will still be generated as usual.
</Note>

## Tracking free trials

Dub supports tracking [subscription free trials](https://docs.stripe.com/billing/subscriptions/trials) as lead events on Dub. This is useful for products with free trials since you might want to track trial activations as part of your attribution flow.

To enable free trial tracking, go to your Stripe integration settings and enable the **Track Free Trials** option:

<Frame>
  <img alt="The Stripe integration settings page" />
</Frame>

Optionally, you can also configure the integration to track the [provisioned quantity](https://docs.stripe.com/billing/subscriptions/quantities) in the subscription as separate lead events.

This is useful if you have a [lead-based reward](/help/article/partner-rewards#configuring-reward-types) for your [partner program](https://dub.co/partners) and want to reward partners for each unit of the subscription that their customers purchase (e.g. \$50 per lead/provisioned seat).

<Tip>
  To differentiate between [manually tracked lead events](/docs/quickstart/server#tracking-lead-events) and free trial lead events for lead reward types, use the `Customer` `Source` [reward condition](/help/article/partner-rewards#adding-reward-conditions) to filter for `free trial` lead events:

  <Frame>
    <img alt="The lead reward free trial condition" />
  </Frame>
</Tip>

## Tax handling

When tracking sale conversions from Stripe, Dub automatically excludes taxes from the final sale amount to ensure accurate revenue reporting.

For **checkout sessions**, Dub calculates the sale amount by subtracting the tax amount from the total:

```javascript theme={null}
// Sale amount calculation for checkout sessions
saleAmount = amount_total - total_details.amount_tax;
```

For **invoices**, Dub uses the `total_excluding_tax` field when available:

```javascript theme={null}
// Sale amount calculation for invoices
saleAmount = total_excluding_tax ?? amount_paid;
```

This ensures that the sale amounts recorded in Dub reflect the actual revenue before taxes, providing more accurate metrics for:

* Revenue tracking and reporting
* Partner commission calculations
* Analytics and conversion metrics

<Note>
  Tax amounts are automatically excluded from all sale events tracked through
  the Stripe integration, including one-time purchases, subscriptions, and
  recurring invoices.
</Note>

## Currency conversion support

If you're using [Stripe's Adaptive Pricing](https://docs.stripe.com/payments/checkout/adaptive-pricing) feature, Dub will record the sale amount using the currency of your Stripe account:

```json checkout.session.completed theme={null}
// Stripe checkout.session.completed event payload
{
  "id": "{{EVENT_ID}}",
  "object": "event",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "{{SESSION_ID}}",
      "object": "checkout.session",
      "currency": "cad",
      "amount_subtotal": 2055,
      "amount_total": 2055,
      "currency_conversion": {
        "amount_subtotal": 1500,
        "amount_total": 1500, // this is the amount that Dub will record
        "source_currency": "usd", // the currency of your Stripe account
        "fx_rate": "1.37"
      }
    }
  }
}
```

If you're not using Stripe Adaptive Pricing, Dub will record the sale amount in the default currency of your Dub workspace. This means that if you pass a different currency, it will be automatically converted to USD for reporting consistency – using the latest foreign exchange rates.

```json checkout.session.completed theme={null}
// Stripe checkout.session.completed event payload
{
  "id": "{{EVENT_ID}}",
  "object": "event",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "{{SESSION_ID}}",
      "object": "checkout.session",
      "currency": "cad",
      "amount_subtotal": 2055,
      "amount_total": 2055 // this will be converted from CAD to USD
    }
  }
}
```

<Note>
  The default currency for all Dub workspaces is currently set to `USD`. We will
  add the ability to customize that in the future.
</Note>

## Multiple line items per invoice

When a Stripe invoice or checkout session contains multiple line items (e.g. a base subscription plus an add-on, or several products purchased in a single transaction), Dub processes each line item individually to calculate the correct commission amount.

For each line item, Dub:

1. Resolves the Stripe product ID for that line item.
2. Matches it against any [product-specific reward modifiers](/help/article/partner-rewards#product-spotlight) configured for the partner's group.
3. Calculates the commission for that line item using the matching reward rate (falling back to the default reward if no modifier matches).

The final commission recorded for the invoice is the sum of the per-line-item commissions.

<Tip>
  For example, if a partner has a `40%` reward for `prod_gold` and a `10%`
  reward for everything else, an invoice with `$100` of `prod_gold` and `$50` of
  `prod_addon` will generate a commission of `($100 × 40%) + ($50 × 10%) = $45`.
</Tip>

The total `saleAmount` recorded on Dub still reflects the full invoice total (after [tax exclusion](#tax-handling) and [currency conversion](#currency-conversion-support)), so your revenue analytics remain accurate.

## View conversion results

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## Example apps

<CardGroup>
  <Card title="Dub + Stripe Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/stripe">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Local development
Source: https://dub.co/docs/local-development

A guide on how to run Dub's codebase locally.

<Frame>
  <img alt="Dub Logo on a gradient background" />
</Frame>

## Introduction

Dub's codebase is set up in a monorepo (via [Turborepo](https://turbo.build/repo)) and is fully [open-source on GitHub](https://github.com/dubinc/dub).

Here's the monorepo structure:

```
apps
├── web
packages
├── cli
├── email
├── embeds
├── prisma
├── stripe-app
├── tailwind-config
├── tinybird
├── tsconfig
├── ui
├── utils
```

The `apps` directory contains the code for:

* `web`: The entirety of Dub's application ([app.dub.co](https://app.dub.co)) + our link redirect infrastructure.

The `packages` directory contains the code for:

* `cli`: A CLI for easily shortening URLs with the Dub API.
* `email`: Dub's email application with function to send emails and templates.
* `embeds`: A package used embed Dub's referral dashboard.
* `prisma`: Prisma Configuration for Dub's web-app.
* `stripe-app`: The Stripe app for dub conversions.
* `tailwind-config`: The Tailwind CSS configuration for Dub's web app.
* `tinybird`: Dub's Tinybird configuration.
* `tsconfig`: The TypeScript configuration for Dub's web app.
* `ui`: Dub's UI component library.
* `utils`: A collection of utility functions and constants used across Dub's codebase.

## How `app.dub.co` works

Dub's web app is built with [Next.js](https://nextjs.org) and [TailwindCSS](https://tailwindcss.com).

It also utilizes code from the `packages` directory, specifically the `@dub/ui` and `@dub/utils` packages.

All of the code for the web app is located in here: [`main`/apps/web/app/app.dub.co](https://github.com/dubinc/dub/tree/main/apps/web/app/app.dub.co). This is using the Next.js [route group pattern](https://nextjs.org/docs/app/building-your-application/routing/route-groups).

There's also the API server, which is located in here: [`main`/apps/web/app/api](https://github.com/dubinc/dub/tree/main/apps/web/app/api)

When you run `pnpm dev` to start the development server, the app will be available at [http://localhost:8888](http://localhost:8888). The reason we use `localhost:8888` and not `app.localhost:8888` is because Google OAuth doesn't allow you to use localhost subdomains.

## How link redirects work on Dub

Link redirects on Dub are powered by [Next.js Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware).

To handle high traffic, we use Redis to cache every link's metadata when it's first created. This allows us to serve redirects without hitting our MySQL database.

Here's the code that powers link redirects: [`main`/apps/web/lib/middleware/link.ts](https://github.com/dubinc/dub/blob/main/apps/web/lib/middleware/link.ts)

## Running Dub locally

To run Dub locally, you'll need to set up the following:

* A [Tinybird](https://www.tinybird.co/) account
* An [Upstash](https://upstash.com/) account
* A [PlanetScale](https://planetscale.com/)-compatible MySQL database

Watch this video from our friends at Tinybird to learn how to set up Dub locally:

<iframe title="YouTube video player" />

## Step 1: Local setup

First, you'll need to clone the Dub repo and install the dependencies.

<Steps>
  <Step title="Clone the repo">
    First, clone the [Dub repo](https://d.to/github) into a public GitHub repository.

    ```bash Terminal theme={null}
    git clone https://github.com/dubinc/dub.git
    ```
  </Step>

  <Step title="Install dependencies">
    Run the following command to install the dependencies:

    ```bash Terminal theme={null}
    pnpm i
    ```
  </Step>

  <Step title="Build internal packages">
    Execute the command below to compile all internal packages:

    ```bash Terminal theme={null}
    pnpm -r --filter "./packages/**" build
    ```
  </Step>

  <Step title="Set up environment variables">
    Copy the `.env.example` file from `./apps/web` to `.env` by executing the following command from `apps/web`:

    ```bash Terminal theme={null}
    cp .env.example .env
    ```

    You'll be updating this `.env` file with your own values as you progress through the setup.
  </Step>
</Steps>

## Step 2: Set up Tinybird Clickhouse database

Next, you'll need to set up the [Tinybird](https://tinybird.co) Clickhouse database. This will be used to store time-series click events data.

<Steps>
  <Step title="Create Tinybird Workspace">
    In your [Tinybird](https://tinybird.co/) account, create a new Workspace. For this guide, we will use the `us-east-1` region.

    Copy your `admin` [Auth Token](https://www.tinybird.co/docs/concepts/auth-tokens.html). Paste this token as the `TINYBIRD_API_KEY` environment variable in your `.env` file.

    <Tip>
      Alternatively, you can set up a [local Tinybird container](https://www.tinybird.co/docs/cli/local-container) for local development.
    </Tip>
  </Step>

  <Step title="Install Tinybird CLI and authenticate">
    In your newly-cloned Dub repo, navigate to the `packages/tinybird` directory.

    Install the Tinybird CLI:

    ```bash Terminal theme={null}
    curl https://tinybird.co | sh
    ```

    Then authenticate with Tinybird:

    ```bash Terminal theme={null}
    tb login
    ```
  </Step>

  <Step title="Publish Tinybird datasource and endpoints">
    Run `tb --cloud deploy` to publish the datasource and endpoints to your Tinybird Cloud workspace.

    ```bash Terminal theme={null}
    tb --cloud deploy
    ```

    You should see output like this when the deployment is live:

    ```bash Terminal theme={null}
    Deployment URL: https://cloud.tinybird.co/aws/us-east-1/<workspace-name>/deployments/<deployment-id>
    Job API URL: https://api.us-east.aws.tinybird.co/v0/jobs/<job-id>
    Jobs URL: https://cloud.tinybird.co/aws/us-east-1/<workspace-name>/jobs

    * Deployment submitted. It will be auto-promoted when ready.
    » Waiting for deployment to be ready...
    ✓ Deployment is ready
    » Waiting for deployment to be promoted...
    ✓ Deployment promoted
    ✓ Deployment #1 is live!
    ```
  </Step>

  <Step title="Set up Tinybird API base URL">
    You will then need to update your [Tinybird API base URL](https://www.tinybird.co/docs/api-reference/api-reference.html#regions-and-endpoints) to match the region of your database.

    From the previous step, take note of the **Job API URL** in the deployment output. It should look something like this:

    ```bash Terminal theme={null}
    Job API URL: https://api.us-east.aws.tinybird.co/v0/jobs/<job-id>
    ```

    Copy the base URL before `/v0/jobs` and paste it as the `TINYBIRD_API_URL` environment variable in your `.env` file.

    ```bash Terminal theme={null}
    TINYBIRD_API_URL=https://api.us-east.aws.tinybird.co
    ```
  </Step>
</Steps>

## Step 3: Set up Upstash Redis database

Next, you'll need to set up the [Upstash](https://upstash.com) Redis database. This will be used to cache link metadata and serve link redirects.

<Steps>
  <Step title="Create Upstash database">
    In your [Upstash account](https://console.upstash.com/), create a new database in the `us-east-1` region.

    <Frame>
      <img alt="Upstash Redis database" />
    </Frame>
  </Step>

  <Step title="Set up Upstash Redis environment variables">
    Once your database is created, copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the **REST API** section into your `.env` file.

    <Frame>
      <img alt="Upstash Redis tokens" />
    </Frame>

    Navigate to the [QStash tab](https://console.upstash.com/qstash) and copy `QSTASH_TOKEN`, `QSTASH_CURRENT_SIGNING_KEY`, and `QSTASH_NEXT_SIGNING_KEY` into your `.env` file. You don't need to copy `QSTASH_URL` for local Dub setup.

    <Frame>
      <img alt="Upstash QStash tokens" />
    </Frame>
  </Step>

  <Step title="Optional: Set up Ngrok tunnel">
    If you're testing features that enqueue QStash background jobs locally, you'll need to set up an Ngrok tunnel to expose your local server to the internet. Dub uses QStash for workflows like imports, exports, webhooks, payouts, and other `/api/cron/*` jobs. Without a tunnel, QStash won't be able to call back into your local app.

    Follow [these steps](https://ngrok.com/docs/getting-started/) to setup `ngrok`, and then run the following command to start an Ngrok tunnel at port `8888`:

    ```bash Terminal theme={null}
    ngrok http 8888
    ```

    Copy the `https` URL and paste it as the `NEXT_PUBLIC_NGROK_URL` environment variable in your `.env` file. If you're using a free Ngrok account, this URL can change when you restart Ngrok, so make sure to update `NEXT_PUBLIC_NGROK_URL` whenever it changes.
  </Step>
</Steps>

## Step 4: Set up PlanetScale MySQL database

Next, you'll need to set up a [PlanetScale](https://planetscale.com/)-compatible MySQL database. This will be used to store user data and link metadata. There are two options:

### Option 1: Local MySQL database with PlanetScale simulator (recommended)

You can use a local MySQL database with a PlanetScale simulator. This is the recommended option for local development since it's 100% free.

Prerequisites:

* [Docker](https://www.docker.com/products/docker-desktop)
* [Docker Compose](https://docs.docker.com/compose/install/)

<Steps>
  <Step title="Spin up the docker-compose stack">
    In the terminal, navigate to the `apps/web` directory and run the following command to start the Docker Compose stack:

    ```bash Terminal theme={null}
    docker compose up
    ```

    This will start two containers: one for the MySQL database and another for the PlanetScale simulator.
  </Step>

  <Step title="Set up database environment variables">
    Ensure the following credentials are added to your `.env` file:

    ```
    DATABASE_URL="mysql://root:@localhost:3306/planetscale"
    PLANETSCALE_DATABASE_URL="http://root:unused@localhost:3900/planetscale"
    ```

    Here, we are using the open-source [PlanetScale simulator](https://github.com/mattrobenolt/ps-http-sim) so the application can continue to use the `@planetscale/database` SDK.

    <Tip>
      While we're using two different values in local development, in production or staging environments, you'll only need the `DATABASE_URL` value.
    </Tip>
  </Step>

  <Step title="Generate Prisma client and create database tables">
    In the terminal, navigate to the `apps/web` directory and run the following command to generate the Prisma client:

    ```bash Terminal theme={null}
    pnpm run prisma:generate
    ```

    Then, create the database tables with the following command:

    ```bash Terminal theme={null}
    pnpm run prisma:push
    ```
  </Step>
</Steps>

<Tip>
  The docker-compose setup includes Mailhog, which acts as a mock SMTP server
  and shows received emails in a web UI. You can access the Mailhog web
  interface at [http://localhost:8025](http://localhost:8025). This is useful
  for testing email functionality without sending real emails during local
  development.
</Tip>

### Option 2: PlanetScale hosted database

<Note>
  PlanetScale recently [removed their free
  tier](https://planetscale.com/blog/planetscale-forever), so you'll need to pay
  for this option. A cheaper alternative is to use a [MySQL database on
  Railway](https://railway.app/template/mysql) (\$5/month).
</Note>

<Steps>
  <Step title="Create PlanetScale database">
    In your [PlanetScale account](https://app.planetscale.com/), create a new database.

    Once your database is created, you'll be prompted to select your language or Framework. Select **Prisma**.

    <Frame>
      <img alt="PlanetScale choose framework" />
    </Frame>
  </Step>

  <Step title="Set up PlanetScale environment variables">
    Then, you'll have to create a new password for your database. Once the password is created, scroll down to the **Add credentials to .env** section and copy the `DATABASE_URL` into your `.env` file.

    <Frame>
      <img alt="PlanetScale add credentials" />
    </Frame>
  </Step>

  <Step title="Generate Prisma client and create database tables">
    In the terminal, navigate to the `apps/web` directory and run the following command to generate the Prisma client:

    ```bash Terminal theme={null}
    pnpm run prisma:generate
    ```

    Then, create the database tables with the following command:

    ```bash Terminal theme={null}
    pnpm run prisma:push
    ```
  </Step>
</Steps>

## Step 5: Set up Mailhog

To view emails sent from your application during local development, you'll need to set up [Mailhog](https://github.com/mailhog/MailHog).

<Note>
  If you've already run `docker compose up` as part of the database setup, you
  can skip this step. Mailhog is included in the Docker Compose configuration
  and should already be running.
</Note>

<Steps>
  <Step title="Pull Mailhog Docker image">
    Run the following command to pull the Mailhog Docker image:

    ```bash Terminal theme={null}
    docker pull mailhog/mailhog
    ```
  </Step>

  <Step title="Start Mailhog container">
    Start the Mailhog container with the following command:

    ```bash Terminal theme={null}
    docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
    ```

    This will run Mailhog in the background, and the web interface will be available at [http://localhost:8025](http://localhost:8025).
  </Step>
</Steps>

## Step 6: Set NextAuth secret

Generate a secret by visiting [https://generate-secret.vercel.app/32](https://generate-secret.vercel.app/32). Set the value of `NEXTAUTH_SECRET` in `.env` to this value.

## Step 7: Seed the database (optional)

You can seed the database with sample data for testing and development purposes. This creates a workspace with test users, domains, folders, partners, and other resources.

<Steps>
  <Step title="Run the seed script">
    Navigate to the `apps/web` directory and run the following command:

    ```bash Terminal theme={null}
    pnpm run script dev/seed
    ```

    This will add sample data without deleting any existing data.
  </Step>

  <Step title="Truncate and seed (optional)">
    If you want to start fresh by deleting all existing data before seeding:

    ```bash Terminal theme={null}
    pnpm run script dev/seed --truncate
    ```

    When using `--truncate`, the script will ask for confirmation before deleting any data.
  </Step>
</Steps>

## Step 8: Start the development server

Finally, you can start the development server. This will build the packages + start the app servers.

```bash Terminal theme={null}
pnpm dev
```

The web app (`apps/web`) will be available at [localhost:8888](http://localhost:8888). Additionally, you may access Prisma Studio to manage your MySQL database at [localhost:5555](http://localhost:5555).

### Logging into the application

After seeding the database and starting the development server, you can log in to the application using one of the test users created during the seed process.

<Steps>
  <Step title="Find a test user email">
    Navigate to [http://localhost:5555](http://localhost:5555) (Prisma Studio) and open the **Users** table. You'll find several test users, including `owner@dub-internal-test.com`.
  </Step>

  <Step title="Get the login link">
    Go to [http://localhost:8888/login](http://localhost:8888/login) and use the email login method with one of the test user emails.

    Check your **terminal** where the development server is running. After submitting the login form, you'll see a log message in the following format:

    ```
    Login link: http://localhost:8888/api/auth/callback/email?callbackUrl=...
    ```
  </Step>

  <Step title="Complete login">
    Copy the login link from the console and paste it into your browser's address bar. You'll be automatically logged in.
  </Step>
</Steps>

### Testing your shortlinks locally

Use the following url structure to ensure event tracking is working, and to populate analytics data, replacing `<shortlink-key>` with the shortlink key you've created.

```
http://dub.localhost:8888/<shortlink-key>
```

## Troubleshooting

### 500 error on `/api/workspaces/[idOrSlug]` route

If you're receiving a 500 error when accessing workspace-related pages, it may be due to missing Stripe API keys. Check your application logs for Stripe-related errors.

For **local development only**, you can add mock Stripe keys to your `apps/web/.env` file:

```bash .env theme={null}
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=123
STRIPE_SECRET_KEY=123
STRIPE_WEBHOOK_SECRET=123

# Stripe App webhook events
STRIPE_APP_WEBHOOK_SECRET=123
```

<Warning>
  These mock keys are for local development only and should never be used in production environments.
</Warning>

## Running E2E tests locally

To run end-to-end tests locally, you'll need to configure additional environment variables and generate an API token.

<Steps>
  <Step title="Add E2E environment variables">
    Add the following environment variables to your `apps/web/.env` file:

    ```bash .env theme={null}
    # E2E testing
    CI=true
    E2E_BASE_URL=http://localhost:8888
    E2E_TOKEN=your_token_here
    E2E_TOKEN_MEMBER=your_token_here
    E2E_TOKEN_OLD=your_token_here
    E2E_PUBLISHABLE_KEY=your_token_here
    ```
  </Step>

  <Step title="Generate an API token">
    1. Start your development server and log in to the application
    2. Navigate to [http://localhost:8888/acme/settings/tokens](http://localhost:8888/acme/settings/tokens)
    3. Generate a new API token with full access permissions
    4. Replace all instances of `your_token_here` in your `.env` file with the generated token
  </Step>

  <Step title="About the CI variable">
    The `CI=true` variable is used because some tests are designed to run in CI environments. Setting this to `true` allows you to run these tests locally for development and debugging purposes.
  </Step>
</Steps>


# Dub MCP Server
Source: https://dub.co/docs/mcp-server

Use the Dub MCP server to manage your Dub resources with the AI agent of your choice.

MCP is an open protocol that standardizes how applications provide context to LLMs. Among other benefits, it provides LLMs tools to act on your behalf. Dub offers both a [remote MCP server](#remote-mcp-server-recommended) and a [local MCP server](#local-mcp-server).

<Frame>
  <img alt="MCP Server" />
</Frame>

<Tip>
  Looking for the [Dub Links](https://dub.co/links) MCP server? Check out the
  [Dub Links MCP server docs](https://mcp.dub.sh/mcp/dub-links).
</Tip>

## Remote MCP Server (recommended)

Dub's MCP server is hosted at:

```
https://mcp.dub.sh/mcp/dub-partners
```

Connect any MCP client that supports remote servers (Streamable HTTP). There's nothing to install and no local process to run, which makes it the best option for web-based clients like Claude and hosted agent platforms.

When you connect, your client opens a browser window to log in to Dub and approve access using OAuth.

<Tabs>
  <Tab title="Claude">
    You can install the Dub MCP server as a [custom connector in
    Claude](https://claude.ai/new#settings/customize-connectors):

    <Frame>
      <video />
    </Frame>

    To install only the MCP server, add it manually in Claude (web or desktop) from **Settings** > **Connectors** > **Add custom connector**:

    ```
    https://mcp.dub.sh/mcp/dub-partners
    ```
  </Tab>

  <Tab title="ChatGPT">
    You can install the Dub MCP server as a [ChatGPT
    plugin](https://chatgpt.com/plugins):

    <Tip>
      Make sure to enable [Developer Mode](https://developers.openai.com/api/docs/guides/developer-mode) in ChatGPT first before installing the plugin.
    </Tip>

    <Frame>
      <video />
    </Frame>
  </Tab>

  <Tab title="Cursor">
    Open the command palette and choose **Cursor Settings** → **MCP** → **Add new global MCP server**.

    ```json theme={null}
    {
      "mcpServers": {
        "dub": {
          "url": "https://mcp.dub.sh/mcp/dub-partners"
        }
      }
    }
    ```
  </Tab>

  <Tab title="Copilot">
    To use GitHub Copilot in VS Code, add the following to your `settings.json`:

    ```json theme={null}
    {
      "mcp": {
        "servers": {
          "dub": {
            "type": "http",
            "url": "https://mcp.dub.sh/mcp/dub-partners"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Windsurf">
    In Windsurf's Settings, navigate to **Agents** > **MCP servers**, and click **+ Add** to add a new server.

    ```json theme={null}
    {
      "mcpServers": {
        "dub": {
          "serverUrl": "https://mcp.dub.sh/mcp/dub-partners"
        }
      }
    }
    ```
  </Tab>

  <Tab title="Warp">
    In Warp's Settings, navigate to **Agents** > **MCP servers**, and click **+ Add** to add a new server.

    ```json theme={null}
    {
      "dub": {
        "serverUrl": "https://mcp.dub.sh/mcp/dub-partners"
      }
    }
    ```
  </Tab>
</Tabs>

***

*If your client runs somewhere a browser login isn't possible* (a server, CI, or a headless agent), pass a [Dub API key](/docs/api-reference/authentication#api-keys) in the `Mcp-Dub-Token` header instead of using OAuth.

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={null}
    claude mcp add dub-partners \
      --transport http https://mcp.dub.sh/mcp/dub-partners \
      --header "Mcp-Dub-Token: dub_xxxxxx"
    ```
  </Tab>

  <Tab title="JSON config">
    For clients configured with JSON (Cursor, Windsurf, and others), add a `Mcp-Dub-Token` header:

    ```json theme={null}
    {
      "mcpServers": {
        "dub": {
          "url": "https://mcp.dub.sh/mcp/dub-partners",
          "headers": {
            "Mcp-Dub-Token": "dub_xxxxxx"
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Local MCP Server

If your MCP client doesn't support connecting directly to a remote HTTP endpoint, use [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) to run a local process that proxies requests to Dub's hosted server. You can integrate it into any supported MCP client using `npx`. You'll need a [Dub API key](/docs/api-reference/authentication#api-keys).

Choose your client below to get started. Remember to replace `dub_xxxxxx` with your actual API key.

### Stdio Transport (Default)

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={null}
    claude mcp add dub-partners \
      --env MCP_DUB_TOKEN=dub_xxxxxx \
      -- npx -y mcp-remote@0.1.25 https://mcp.dub.sh/mcp/dub-partners \
      --header "Mcp-Dub-Token:\${MCP_DUB_TOKEN}"
    ```
  </Tab>

  <Tab title="Codex">
    ````bash codex mcp add dub-partners \ --env MCP_DUB_TOKEN=dub_xxxxxx \ -- npx theme={null}
    -y mcp-remote@0.1.25 https://mcp.dub.sh/mcp/dub-partners \ --header
    "Mcp-Dub-Token:\${MCP_DUB_TOKEN}" ```
    </Tab>

    <Tab title="Cursor">
      Open the command palette and choose **Cursor Settings** → **MCP** → **Add new global MCP server**.

      ```json
      {
        "mcpServers": {
          "dub": {
            "command": "npx",
            "args": [
              "-y",
              "mcp-remote@0.1.25",
              "https://mcp.dub.sh/mcp/dub-partners",
              "--header",
              "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
            ],
            "env": {
              "MCP_DUB_TOKEN": "dub_xxxxxx"
            }
          }
        }
      }
    ````
  </Tab>

  <Tab title="Claude Desktop">
    Open **Claude Desktop** settings → **Developer** tab → **Edit Config** and add the following:

    ```json theme={null}
    {
      "mcpServers": {
        "dub": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote@0.1.25",
            "https://mcp.dub.sh/mcp/dub-partners",
            "--header",
            "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
          ],
          "env": {
            "MCP_DUB_TOKEN": "dub_xxxxxx"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Copilot">
    To use GitHub Copilot in VS Code, add the following to your `settings.json`:

    ```json theme={null}
    {
      "mcp": {
        "servers": {
          "dub": {
            "command": "npx",
            "args": [
              "-y",
              "mcp-remote@0.1.25",
              "https://mcp.dub.sh/mcp/dub-partners",
              "--header",
              "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
            ],
            "env": {
              "MCP_DUB_TOKEN": "dub_xxxxxx"
            }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Gemini CLI">
    ```json theme={null}
    {
      "mcpServers": {
        "dub": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote@0.1.25",
            "https://mcp.dub.sh/mcp/dub-partners",
            "--header",
            "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
          ],
          "env": {
            "MCP_DUB_TOKEN": "dub_xxxxxx"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="OpenCode">
    Add to your `opencode.json` config:

    ```json theme={null}
    {
      "$schema": "https://opencode.ai/config.json",
      "mcp": {
        "dub": {
          "type": "local",
          "command": [
            "npx",
            "-y",
            "mcp-remote@0.1.25",
            "https://mcp.dub.sh/mcp/dub-partners",
            "--header",
            "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
          ],
          "enabled": true,
          "environment": {
            "MCP_DUB_TOKEN": "dub_xxxxxx"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Windsurf">
    ```json theme={null}
    {
      "mcpServers": {
        "dub": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote@0.1.25",
            "https://mcp.dub.sh/mcp/dub-partners",
            "--header",
            "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
          ],
          "env": {
            "MCP_DUB_TOKEN": "dub_xxxxxx"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Warp">
    In Warp's Settings, navigate to **Agents** > **MCP servers**, and click **+ Add** to add a new server.

    ```json theme={null}
    {
      "dub": {
        "command": "npx",
        "args": [
          "-y",
          "mcp-remote@0.1.25",
          "https://mcp.dub.sh/mcp/dub-partners",
          "--header",
          "Mcp-Dub-Token:${MCP_DUB_TOKEN}"
        ],
        "env": {
          "MCP_DUB_TOKEN": "dub_xxxxxx"
        }
      }
    }
    ```
  </Tab>
</Tabs>

### Environment variables

* `MCP_DUB_TOKEN`: Your Dub API key (used by `mcp-remote` and referenced in client configs as `${MCP_DUB_TOKEN}`)

<Info>
  Store your API key in the `env` block or an environment variable rather than
  hardcoding it in shared config files when possible.
</Info>

## MCP Server tools

The Dub [MCP server](https://mcp.dub.sh/mcp/dub-partners) gives your AI agent native access to the full Dub platform through a single integration. You can manage your partner program, review applications, update commissions, and track attribution using natural language.

* **Partners** — Create or update partners, list all partners, deactivate, or ban partners from your program
* **Partner links** — Create and upsert links for enrolled partners
* **Applications** — List, approve, and reject pending partner applications
* **Analytics** — Retrieve analytics and list events for any partner, [partner groups](/help/article/partner-groups), or their specific links
* **Customers** — List, retrieve, update, and delete customers
* **Tracking** — Track lead, sale, and deep link open events
* **Bounties** — List, approve, and reject bounty submissions
* **Commissions** — List and update commissions (e.g. for refunds or fraud)
* **Payouts** — List payouts for your partner program

Here are some real examples of what your agent can do with these tools:

* Triage pending partner applications and approve or reject them in bulk
* Adjust commissions after a refund or flag suspicious activity
* Pull attribution analytics for a top partner or [partner group](/help/article/partner-groups)
* Create partner links and track lead or sale conversion events


# Introduction
Source: https://dub.co/docs/partner-api-reference/introduction

Learn how to use Dub's Partner API to programmatically manage your partner account.

<Note>Coming soon.</Note>


# Embedded referral dashboard
Source: https://dub.co/docs/partners/embedded-referrals

Learn how to create an embedded referral dashboard with Dub for your users to join your partner program without leaving your app.

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

With [Dub Partners](https://dub.co/partners), you can build an embedded referral dashboard that lives directly inside your application in just a few lines of code.

This way, your users can automatically enroll in your partner program **without needing to leave your app and sign up on a third-party platform**.

<Frame>
  <video />
</Frame>

In this guide, we'll walk you through the steps to get started with Dub's embedded referral dashboard feature.

<Tip>
  Looking to set up non-monetary rewards for your user referral partners? [Read
  the guide](/help/article/non-monetary-rewards) on how to set that up.
</Tip>

## Example App

Before we dive in, here's an open-source example app showing Dub's embedded referral dashboard in action: [acme.dub.sh](https://acme.dub.sh)

<Frame>
  <video />
</Frame>

You can also view the source code for the example app on [GitHub](https://github.com/dubinc/examples/tree/main/embed/referrals):

<Card title="Dub embedded referral dashboard demo app" icon="github" href="https://github.com/dubinc/examples/tree/main/embed/referrals">
  View the source code for [acme.dub.sh](https://acme.dub.sh) on GitHub.
</Card>

## Step 1: Generate embed token

First, you need to create a server API route that generates a public token, which will be used by the embedded referral dashboard to fetch real-time conversions and earnings data from the client-side.

<Frame>
  <img alt="A diagram of the embed token flow" />
</Frame>

### Using server-side SDKs

If you're using our [server-side SDKs](/docs/sdks/overview), you can generate an embed token using the `dub.embedTokens.referrals` method.

<CodeGroup>
  ```ts TypeScript theme={null}
  const { publicToken } = await dub.embedTokens.referrals({
    tenantId: user.id, // the user's ID within your application
    partner: {
      name: user.name, // the user's name
      email: user.email, // the user's email
      image: user.image, // the user's image/avatar
      tenantId: user.id, // the user's ID within your application
      groupId: "grp_xxxxxx", // optional: the partner's group ID on Dub (https://d.to/groups)
    },
  });
  ```

  ```python Python theme={null}
  from dub import Dub

  with Dub(
      token="DUB_API_KEY",
  ) as d_client:
      res = d_client.embed_tokens.referrals(request={
          "tenant_id": user.id,  # the user's ID within your application
          "partner": {
              "name": user.name,  # the user's name
              "email": user.email,  # the user's email
              "image": user.image,  # the user's image/avatar
              "tenant_id": user.id,  # the user's ID within your application
              "group_id": "grp_xxxxxx", # optional: the partner's group ID on Dub (https://d.to/groups)
          },
      })

      # Handle response
      print(res.public_token)
  ```

  ```go Go theme={null}
  package main

  import(
      "context"
      dubgo "github.com/dubinc/dub-go"
      "github.com/dubinc/dub-go/models/operations"
      "log"
  )

  func main() {
      ctx := context.Background()

      s := dubgo.New(
          dubgo.WithSecurity("DUB_API_KEY"),
      )

      res, err := s.EmbedTokens.Referrals(ctx, &operations.CreateReferralsEmbedTokenRequestBody{
          TenantID: user.ID, // the user's ID within your application
          Partner: &operations.Partner{
              Name: user.Name, // the user's name
              Email: user.Email, // the user's email
              Image: user.Image, // the user's image/avatar
              TenantID: user.ID, // the user's ID within your application
              GroupID: "grp_xxxxxx", // optional: the partner's group ID on Dub (https://d.to/groups)
          },
      })
      if err != nil {
          log.Fatal(err)
      }
      if res != nil {
          // Handle response
          log.Printf("Public token: %s", res.PublicToken)
      }
  }
  ```

  ```php PHP theme={null}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Dub;
  use Dub\Models\Operations;

  $sdk = Dub\Dub::builder()
      ->setSecurity('DUB_API_KEY')
      ->build();

  $request = new Operations\CreateReferralsEmbedTokenRequestBody(
      tenantId: $user->id, // the user's ID within your application
      partner: new Operations\Partner(
          name: $user->name, // the user's name
          email: $user->email, // the user's email
          image: $user->image, // the user's image/avatar
          tenantId: $user->id, // the user's ID within your application
          groupId: "grp_xxxxxx", // optional: the partner's group ID on Dub (https://d.to/groups)
      ),
  );

  $response = $sdk->embedTokens->referrals(
      request: $request
  );

  if ($response->object !== null) {
      // Handle response
      echo $response->object->publicToken;
  }
  ```

  ```ruby Ruby theme={null}
  require 'dub'

  s = ::OpenApiSDK::Dub.new(
      security: ::OpenApiSDK::Shared::Security.new(
          token: "DUB_API_KEY",
      ),
  )

  req = ::OpenApiSDK::Operations::CreateReferralsEmbedTokenRequestBody.new(
      tenant_id: user.id, # the user's ID within your application
      partner: ::OpenApiSDK::Operations::Partner.new(
          name: user.name, # the user's name
          email: user.email, # the user's email
          image: user.image, # the user's image/avatar
          tenant_id: user.id, # the user's ID within your application
          group_id: "grp_xxxxxx", # optional: the partner's group ID on Dub (https://d.to/groups)
      ),
  )

  res = s.embed_tokens.referrals(req)

  if !res.object.nil?
      # Handle response
      puts res.object.public_token
  end
  ```
</CodeGroup>

### Using REST API

If you're not using our server-side SDKs, you can generate an embed token using our REST API instead (via the [`POST /tokens/embed/referrals`](/docs/api-reference/endpoint/create-referrals-embed-token) endpoint).

```js JavaScript theme={null}
const response = await fetch("https://api.dub.co/tokens/embed/referrals", {
  method: "POST",
  body: JSON.stringify({
    tenantId: user.id, // the user's ID within your application
    partner: {
      name: user.name, // the user's name
      email: user.email, // the user's email
      image: user.image, // the user's image/avatar
      tenantId: user.id, // the user's ID within your application
      groupId: "grp_xxxxxx", // optional: the partner's group ID on Dub (https://d.to/groups)
    },
  }),
});

const data = await response.json();

const { publicToken } = data;
```

Refer to the [full API reference](/docs/api-reference/endpoint/create-referrals-embed-token) to learn more about the properties you can pass to the `POST /tokens/embed/referrals` endpoint.

<Note>
  When you call this endpoint, it first attempts to find an existing enrollment
  using the provided `tenantId`. If no enrollment is found, it resolves the
  partner by `email` and creates a new enrollment automatically. This means you
  don't need to handle enrollment logic separately – the endpoint guarantees a
  valid enrollment and returns a usable embed token every time.
</Note>

## Step 2: Install the embed

Then, with the `publicToken` from Step 1, you can install and initialize the embedded referral dashboard. There are two ways to do this:

### React component

First, install the [NPM package](https://www.npmjs.com/package/@dub/embed-react):

<CodeGroup>
  ```bash npm theme={null}
  npm install @dub/embed-react
  ```

  ```bash yarn theme={null}
  yarn add @dub/embed-react
  ```

  ```bash pnpm theme={null}
  pnpm add @dub/embed-react
  ```

  ```bash bun theme={null}
  bun add @dub/embed-react
  ```
</CodeGroup>

Then use the component in your React application:

```tsx theme={null}
import { useState, useEffect } from "react";
import { DubEmbed } from "@dub/embed-react";

export default function App() {
  const [publicToken, setPublicToken] = useState("");

  useEffect(() => {
    const fetchPublicToken = async () => {
      // fetching from the server API route you created in Step 1
      const response = await fetch("/api/embed-token");
      const data = await response.json();
      setPublicToken(data.publicToken);
    };

    fetchPublicToken();
  }, []);

  if (!publicToken) {
    return <div>Loading...</div>;
  }

  return <DubEmbed data="referrals" token={publicToken} />;
}
```

### Iframe embed

Alternatively, if you're not using React (or you're not on React `v18.2.0` or higher), you can add the iframe directly to your HTML:

```tsx theme={null}
import { useState, useEffect } from "react";

export default function App() {
  const [publicToken, setPublicToken] = useState("");

  useEffect(() => {
    const fetchPublicToken = async () => {
      // fetching from the server API route you created in Step 1
      const response = await fetch("/api/embed-token");
      const data = await response.json();
      setPublicToken(data.publicToken);
    };

    fetchPublicToken();
  }, []);

  if (!publicToken) {
    return <div>Loading...</div>;
  }

  return (
    <iframe
      src={`https://app.dub.co/embed/referrals?token=${publicToken}`}
      allow="clipboard-write"
      class="h-screen w-full"
    />
  );
}
```

## Embed options

The embedded referral dashboard supports the following options for styling and behavior:

<ParamField type="referrals | analytics">
  The type of embed to use. In this case, we're using the `referrals` type.
</ParamField>

<ParamField type="light | dark | system">
  The theme of the embed.
</ParamField>

<ParamField type="JSON-stringified object">
  Available options:

  * `backgroundColor`: The background color of the embed.
</ParamField>

Depending on the embed type, you can use the following examples to initialize the embed options:

<CodeGroup>
  ```tsx React component theme={null}
  import { DubEmbed } from "@dub/embed-react";

  const publicToken = "...";

  <DubEmbed
    data="referrals"
    token={publicToken}
    theme="light"
    themeOptions={{
      backgroundColor: "#F5F5F5",
    }}
  />;
  ```

  ```tsx iFrame embed theme={null}
  const publicToken = "...";

  const iframeUrl = "https://app.dub.co/embed/referrals";

  const iframeParams = new URLSearchParams({
    token: publicToken,
    theme: "light",
    themeOptions: JSON.stringify({ backgroundColor: "#F5F5F5" }),
  });

  <iframe
    src={`${iframeUrl}?${iframeParams.toString()}`}
    allow="clipboard-write"
    class="h-screen w-full"
  />;
  ```
</CodeGroup>


# Event types
Source: https://dub.co/docs/postbacks/event-types

List of supported postback event types and their payloads

Postbacks send real-time notifications when events happen in your Dub partner account. All postback payloads follow this structure:

```json postback-payload.json theme={null}
{
  "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
  "event": "lead.created",
  "createdAt": "2024-08-26T16:41:52.346Z",
  "data": {}
}
```

* **id** – Unique event ID
* **event** – Event type (e.g. `lead.created`)
* **createdAt** – ISO 8601 timestamp when the event was created
* **data** – Event-specific payload

## Postback events

Postbacks only fire for events tied to **your referral links**. Here are the postback events that are available:

| Event Type                                                        | Description                                                                                                           |
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [`lead.created`](/docs/postbacks/events/lead-created)             | Occurs when a **new lead is tracked** via your referral link.                                                         |
| [`sale.created`](/docs/postbacks/events/sale-created)             | Occurs when a **new sale is tracked** via your referral link.                                                         |
| [`commission.created`](/docs/postbacks/events/commission-created) | Occurs when a **new commission is generated** for you — from a tracked conversion or created manually by the program. |


# commission.created
Source: https://dub.co/docs/postbacks/events/commission-created



Event triggered when a [new commission is created](/docs/api-reference/commissions/list) for you.

<WebhookResponseBodyParameters type="commission.created">
  <ParamField type="string">
    Unique identifier for the commission.
  </ParamField>

  <ParamField type="string">
    Commission type (e.g. <code>sale</code>).
  </ParamField>

  <ParamField type="number">
    Sale/order amount in cents.
  </ParamField>

  <ParamField type="number">
    Commission earnings in cents.
  </ParamField>

  <ParamField type="string">
    Currency code (e.g. <code>usd</code>).
  </ParamField>

  <ParamField type="string">
    Commission status (e.g. <code>pending</code>).
  </ParamField>

  <ParamField type="string | null">
    Optional description (e.g. for manual commissions).
  </ParamField>

  <ParamField type="number">
    Quantity (e.g. number of items).
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the commission was created.
  </ParamField>

  <ParamField type="object">
    The referral link the commission is associated with.

    <Expandable title="link object">
      <ParamField type="string">
        Unique identifier for the link.
      </ParamField>

      <ParamField type="string">
        The short link URL.
      </ParamField>

      <ParamField type="string">
        Domain of the short link.
      </ParamField>

      <ParamField type="string">
        Unique key of the short link.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The customer associated with the conversion.

    <Expandable title="customer object">
      <ParamField type="string">
        Unique identifier for the customer.
      </ParamField>

      <ParamField type="string">
        Country code.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the customer was first seen.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp of the customer's first sale, if any.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp when the subscription was canceled, if any.
      </ParamField>
    </Expandable>
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_64dv6vxYVgltzJBKc9ujJ1ghL",
    "event": "commission.created",
    "createdAt": "2025-07-16T10:48:15.468Z",
    "data": {
      "id": "cm_1K09DJTBCRT24P6BRD515CK29",
      "type": "sale",
      "amount": 50000,
      "earnings": 10000,
      "currency": "usd",
      "status": "pending",
      "description": null,
      "quantity": 1,
      "createdAt": "2025-07-16T10:48:14.722Z",
      "link": {
        "id": "cm0lcuvtz000xcutmqw4a7wi3",
        "shortLink": "https://dub.sh/track-test",
        "domain": "dub.sh",
        "key": "track-test"
      },
      "customer": {
        "id": "cus_1K09DJDEACR47NPYC93RM43WF",
        "country": "US",
        "createdAt": "2025-07-16T10:48:01.739Z",
        "firstSaleAt": "2025-07-18T10:48:01.739Z",
        "subscriptionCanceledAt": null
      }
    }
  }
  ```
</ResponseExample>


# lead.created
Source: https://dub.co/docs/postbacks/events/lead-created



Event triggered when a [new lead is tracked](/docs/api-reference/track/lead) via your referral link.

<WebhookResponseBodyParameters type="lead.created">
  <ParamField type="string">
    Name of the tracked event (e.g. <code>Signup</code>).
  </ParamField>

  <ParamField type="object">
    The click event that led to the lead.

    <Expandable title="click object">
      <ParamField type="string">
        Unique identifier for the click.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the click occurred.
      </ParamField>

      <ParamField type="string">
        Destination URL of the click.
      </ParamField>

      <ParamField type="string">
        Country code.
      </ParamField>

      <ParamField type="string">
        City name.
      </ParamField>

      <ParamField type="string">
        Region or datacenter identifier.
      </ParamField>

      <ParamField type="string">
        Continent code.
      </ParamField>

      <ParamField type="string">
        Device type (e.g. <code>Desktop</code>).
      </ParamField>

      <ParamField type="string">
        Browser name.
      </ParamField>

      <ParamField type="string">
        Operating system.
      </ParamField>

      <ParamField type="string">
        Referrer source.
      </ParamField>

      <ParamField type="string">
        Referrer URL.
      </ParamField>

      <ParamField type="boolean">
        Whether the click came from a QR code.
      </ParamField>

      <ParamField type="string">
        IP address of the click.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The referral link the lead is associated with.

    <Expandable title="link object">
      <ParamField type="string">
        Unique identifier for the link.
      </ParamField>

      <ParamField type="string">
        The short link URL.
      </ParamField>

      <ParamField type="string">
        Domain of the short link.
      </ParamField>

      <ParamField type="string">
        Unique key of the short link.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The customer that signed up.

    <Expandable title="customer object">
      <ParamField type="string">
        Unique identifier for the customer.
      </ParamField>

      <ParamField type="string">
        Country code.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the customer was first seen.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp of the customer's first sale, if any.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp when the subscription was canceled, if any.
      </ParamField>
    </Expandable>
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_P343bmyae40ALQYr5HT4vRXRd",
    "event": "lead.created",
    "createdAt": "2025-07-16T10:48:02.000Z",
    "data": {
      "eventName": "Signup",
      "click": {
        "id": "GWGrkftJdYlZD2mq",
        "timestamp": "2025-02-03T09:35:57.926Z",
        "url": "https://github.com/dubinc/dub",
        "country": "US",
        "city": "San Jose",
        "region": "sfo1",
        "continent": "NA",
        "device": "Desktop",
        "browser": "Chrome",
        "os": "Mac OS",
        "referer": "(direct)",
        "refererUrl": "(direct)",
        "qr": false,
        "ip": "198.51.100.42"
      },
      "link": {
        "id": "cm0lcuvtz000xcutmqw4a7wi3",
        "shortLink": "https://dub.sh/track-test",
        "domain": "dub.sh",
        "key": "track-test"
      },
      "customer": {
        "id": "cus_1K09DJDEACR47NPYC93RM43WF",
        "country": "US",
        "createdAt": "2025-07-16T10:48:01.739Z",
        "firstSaleAt": "2025-07-18T10:48:01.739Z",
        "subscriptionCanceledAt": null
      }
    }
  }
  ```
</ResponseExample>


# sale.created
Source: https://dub.co/docs/postbacks/events/sale-created



Event triggered when a [new sale is tracked](/docs/api-reference/track/sale) via your referral link.

<WebhookResponseBodyParameters type="sale.created">
  <ParamField type="string">
    Name of the tracked event (e.g. <code>Subscription</code>).
  </ParamField>

  <ParamField type="object">
    The click event that led to the sale.

    <Expandable title="click object">
      <ParamField type="string">
        Unique identifier for the click.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the click occurred.
      </ParamField>

      <ParamField type="string">
        Destination URL of the click.
      </ParamField>

      <ParamField type="string">
        Country code.
      </ParamField>

      <ParamField type="string">
        City name.
      </ParamField>

      <ParamField type="string">
        Region or datacenter identifier.
      </ParamField>

      <ParamField type="string">
        Continent code.
      </ParamField>

      <ParamField type="string">
        Device type (e.g. <code>Desktop</code>).
      </ParamField>

      <ParamField type="string">
        Browser name.
      </ParamField>

      <ParamField type="string">
        Operating system.
      </ParamField>

      <ParamField type="string">
        Referrer source.
      </ParamField>

      <ParamField type="string">
        Referrer URL.
      </ParamField>

      <ParamField type="boolean">
        Whether the click came from a QR code.
      </ParamField>

      <ParamField type="string">
        IP address of the click.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The referral link the sale is associated with.

    <Expandable title="link object">
      <ParamField type="string">
        Unique identifier for the link.
      </ParamField>

      <ParamField type="string">
        The short link URL.
      </ParamField>

      <ParamField type="string">
        Domain of the short link.
      </ParamField>

      <ParamField type="string">
        Unique key of the short link.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The customer that made the purchase.

    <Expandable title="customer object">
      <ParamField type="string">
        Unique identifier for the customer.
      </ParamField>

      <ParamField type="string">
        Country code.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the customer was first seen.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp of the customer's first sale, if any.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp when the subscription was canceled, if any.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    Details about the recorded sale.

    <Expandable title="sale object">
      <ParamField type="number">
        Sale amount in cents.
      </ParamField>

      <ParamField type="string">
        Currency code (e.g. <code>usd</code>).
      </ParamField>
    </Expandable>
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_WHjyHhqsfYOrlJOOVJSoHXysD",
    "event": "sale.created",
    "createdAt": "2025-07-16T10:48:02.000Z",
    "data": {
      "eventName": "Subscription",
      "click": {
        "id": "GWGrkftJdYlZD2mq",
        "timestamp": "2025-02-03T09:35:57.926Z",
        "url": "https://github.com/dubinc/dub",
        "country": "US",
        "city": "San Jose",
        "region": "sfo1",
        "continent": "NA",
        "device": "Desktop",
        "browser": "Chrome",
        "os": "Mac OS",
        "referer": "(direct)",
        "refererUrl": "(direct)",
        "qr": false,
        "ip": "198.51.100.42"
      },
      "link": {
        "id": "cm0lcuvtz000xcutmqw4a7wi3",
        "shortLink": "https://dub.sh/track-test",
        "domain": "dub.sh",
        "key": "track-test"
      },
      "customer": {
        "id": "cus_1K09DJDEACR47NPYC93RM43WF",
        "country": "US",
        "createdAt": "2025-07-16T10:48:01.739Z",
        "firstSaleAt": "2025-07-18T10:48:01.739Z",
        "subscriptionCanceledAt": null
      },
      "sale": {
        "amount": 100,
        "currency": "usd"
      }
    }
  }
  ```
</ResponseExample>


# Introduction
Source: https://dub.co/docs/postbacks/introduction

Use postbacks to get real-time notifications on events happening across your Dub partner account.

<Note>
  Postbacks are currently in **private beta** and are only available for select
  partners. [Contact us](https://dub.co/contact/support) to get access.
</Note>

Postbacks allow you to listen to real-time events happening across your Dub partner account. With postbacks, you can build custom integrations with Dub, such as:

* Syncing leads to your CRM when someone signs up through your referral link
* Updating your internal dashboards when commissions are created
* Building custom attribution tools that react to lead, sale, and commission events in real time

In this guide, we'll show you how to configure postbacks for your partner account and a list of available events you can listen to.

## Creating a postback

To create a postback for your partner account, you'll need to follow these steps:

Navigate to the [**Postbacks** page](https://partners.dub.co/profile/postbacks) in your Dub partner profile.

Click on **Add Postback** to create a new postback.

<Frame>
  <img alt="Create Postback Modal" />
</Frame>

Fill in the required fields in the postback creation form:

1. **Name**: Give your postback a name that helps you identify it.
2. **URL**: Enter the URL of the endpoint where you want to send the postback. We recommend using [webhook.site](https://webhook.site/) to test your postback.
3. **Triggers**: Select the events you want to listen to. You can select multiple events. Refer to the [Event Types](/docs/postbacks/event-types) section to see the list of available events.

Finally, click on **Create postback** to create the postback. After creation, you'll see your signing secret — make sure to copy and store it securely.

## Viewing postback event logs

We also provide you with a postback event logs page where you can view all the postback events that have been sent to your endpoint in real-time.

To view the postback event logs, select the postback from the [**Postbacks** page](https://partners.dub.co/profile/postbacks) and click on the postback to open its details page.

Here, you'll see a list of all the postback events that have been sent to your endpoint:

<Frame>
  <img alt="Postback Event Logs" />
</Frame>

You can also select a specific event, which will open up a sheet with more details about the event.

## Sending test events

You can send test events to your postback URL to ensure that it's working correctly. To do this:

<Steps>
  <Step title="Go to the postback details page">
    Navigate to the [**Postbacks** page](https://partners.dub.co/profile/postbacks) and select the postback you want to test.

    Click on the postback to open the postback details page.
  </Step>

  <Step title="Open the test event modal">
    Select the `⋮` icon on the top right of the page, and click on **Send test event**.

    This will open up a modal where you can select the event you want to send.

    <Frame>
      <img alt="Send Test Postback Modal" />
    </Frame>
  </Step>

  <Step title="Send the test event">
    Select the event you want to send, and click on **Send test postback**.

    <Frame>
      <img alt="Send Test Postback" />
    </Frame>

    You'll see a success message and receive the postback event in the endpoint you specified.
  </Step>
</Steps>

## Secret rotation

If you need to rotate your postback signing secret (for example, after a suspected compromise), select the `⋮` icon on the postback details page and click on **Rotate secret**. A new secret will be generated.

## Retry Behaviour

If your postback endpoint does not respond with a success status code (2XX), we retry the request to ensure every message will be delivered. You can see all the retry attempts in your postback event logs.

Postbacks are retried until they are successfully delivered – with an exponential backoff to avoid overwhelming your postback endpoint (also known as the "[thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem)"). The delay is capped at 24 hours from the 5th retry attempt onwards.

| Retry attempt | Delay    |
| ------------- | -------- |
| 1st           | 12s      |
| 2nd           | 2m 28s   |
| 3rd           | 30m 8s   |
| 4th           | 6h 7m 6s |
| 5th           | 24h      |
| 6th           | 24h      |
| ...           | ...      |

## IP allowlist

If your endpoint sits behind a firewall or IP allowlist, you'll need to allow traffic from the following IP addresses – these are the IPs that Dub uses to deliver events (and retries):

```
3.125.136.186/32
18.198.95.233/32
3.69.157.89/32
3.69.220.58/32
52.58.170.183/32
3.74.98.166/32
100.51.235.36/32
3.230.190.166/32
3.232.167.47/32
34.198.105.68/32
34.199.12.224/32
18.206.116.131/32
```

<Note>
  This list can potentially change over time. For the most up-to-date list, fetch it directly from [api.upstash.com/v2/qstash/ipv4](https://api.upstash.com/v2/qstash/ipv4).
</Note>


# Verify postback requests
Source: https://dub.co/docs/postbacks/verify-postback-requests

Learn how to verify postback requests to ensure they're coming from Dub.

With signature verification, you can determine if the postback came from Dub, and has not been tampered with in transit.

All postbacks are delivered with a `Dub-Signature` header. Dub generates this header using a secret key that only you and Dub know.

An example header looks like this:

```
Dub-Signature: c9ed6a2abf93f59d761eea69908d8de00f4437b5b6d7cd8b9bf5719cbe61bf46
```

## Finding your postback's signing secret

You can find your postback's signing secret when you create the postback or in the postback details page. Use the **Rotate secret** option if you need to generate a new secret.

Make sure to keep this secret safe by only storing it in a secure environment variable (e.g. `DUB_POSTBACK_SECRET`). Do not commit it to git or add it in any client-side code.

## Verifying a postback request

To verify, you can use the secret key to generate your own signature for each postback. If both signatures match then you can be sure that a received event came from Dub.

The steps required are:

1. Get the raw body of the request.
2. Extract the signature from the `Dub-Signature` header.
3. Calculate the HMAC of the raw body using the `SHA-256` hash function and the secret.
4. Compare the calculated `HMAC` with the one sent in the `Dub-Signature` header. If they match, the postback is verified.

Here's an example of how you can verify a postback request in different languages:

<CodeGroup>
  ```javascript Next.js theme={null}
  export const POST = async (req: Request) => {
    const postbackSignature = req.headers.get("Dub-Signature");
    if (!postbackSignature) {
      return new Response("No signature provided.", { status: 401 });
    }

    // Copy this from the postback details page
    const secret = process.env.DUB_POSTBACK_SECRET;
    if (!secret) {
      return new Response("No secret provided.", { status: 401 });
    }

    // Make sure to get the raw body from the request
    const rawBody = await req.text();

    const computedSignature = crypto
      .createHmac("sha256", secret)
      .update(rawBody)
      .digest("hex");

    if (postbackSignature !== computedSignature) {
      return new Response("Invalid signature", { status: 400 });
    }

    // Handle the postback event
    // ...
  };
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def postback():
      # Get the signature from the header
      postback_signature = request.headers.get('Dub-Signature')
      if not postback_signature:
          abort(401, 'No signature provided.')

      # Copy this from the postback details page
      secret = os.environ.get('DUB_POSTBACK_SECRET')
      if not secret:
          abort(401, 'No secret provided.')

      # Get the raw body of the request
      raw_body = request.data

      # Calculate the HMAC
      computed_signature = hmac.new(
          secret.encode('utf-8'),
          raw_body,
          hashlib.sha256
      ).hexdigest()

      if postback_signature != computed_signature:
          abort(400, 'Invalid signature')

      # Handle the postback event
      # ...

      return 'OK', 200
  ```

  ```go Go theme={null}

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  	"io/ioutil"
  	"net/http"
  	"os"
  )

  func postbackHandler(w http.ResponseWriter, r *http.Request) {
  	// Get the signature from the header
  	postbackSignature := r.Header.Get("Dub-Signature")
  	if postbackSignature == "" {
  		http.Error(w, "No signature provided.", http.StatusUnauthorized)
  		return
  	}

  	// Copy this from the postback details page
  	secret := os.Getenv("DUB_POSTBACK_SECRET")
  	if secret == "" {
  		http.Error(w, "No secret provided.", http.StatusUnauthorized)
  		return
  	}

  	// Read the raw body
  	body, err := ioutil.ReadAll(r.Body)
  	if err != nil {
  		http.Error(w, "Error reading request body", http.StatusInternalServerError)
  		return
  	}

  	// Calculate the HMAC
  	h := hmac.New(sha256.New, []byte(secret))
  	h.Write(body)
  	computedSignature := hex.EncodeToString(h.Sum(nil))

  	if postbackSignature != computedSignature {
  		http.Error(w, "Invalid signature", http.StatusBadRequest)
  		return
  	}

  	// Handle the postback event
  	// ...

  	w.WriteHeader(http.StatusOK)
  	w.Write([]byte("OK"))
  }
  ```
</CodeGroup>

## Why is signature verification important?

Signature verification is a crucial security measure that protects against request forgery and data tampering. Without verification, malicious actors could send fake postback events to your endpoint, potentially triggering unauthorized actions.

The HMAC-SHA256 signature verification process ensures that only Dub can generate valid postback requests and that payloads haven't been modified in transit. This provides both authentication (confirming the sender is Dub) and integrity (ensuring the message hasn't been tampered with).


# Client-side tracking
Source: https://dub.co/docs/quickstart/client

Learn how to track sales conversion events with Dub on the client-side

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's powerful [attribution platform](/docs/concepts/attribution) lets you understand how well your links are translating to actual users and revenue dollars.

<Frame>
  <img alt="Conversion analytics" />
</Frame>

In this guide, we'll learn how to track conversion events with Dub using client-side tracking.

<Warning>
  Client-side conversion tracking comes with the following limitations:

  * **Ad blockers** – Users with ad blockers may prevent tracking scripts from loading.
  * **JavaScript disabled** – Events won’t be tracked if users have JavaScript disabled.
  * **Network issues** – Failed network requests won’t retry automatically.
  * **Privacy concerns** – Some users may block client-side tracking for privacy reasons.

  For more accurate conversion tracking, consider using [server-side conversion tracking](/docs/quickstart/server).
</Warning>

## Step 1: Enable conversion tracking for your links

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Step 2: Install the Dub Analytics script

Then, you'll need to install the Dub Analytics script and set up the necessary configuration for client-side conversion tracking:

<Steps>
  <Step title="Allowlist your site's domain">
    Then, you'll need to allowlist your site's domain to allow the client-side conversion events to be ingested by Dub.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and add your site's domain to the **Allowed Hostnames** list.

    This provides an additional layer of security by ensuring only authorized domains can track conversions using your publishable key.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    You can group your hostnames when adding them to the allow list:

    * `example.com`: Tracks traffic **only** from `example.com`.
    * `*.example.com`: Tracks traffic from **all subdomains** of `example.com`, but **not** from `example.com` itself.

    <Tip>
      When testing things out locally, you can add `localhost` to the **Allowed
      Hostnames** list temporarily. This will allow local events to be ingested by
      Dub. Don't forget to remove it once you're ready to go live!
    </Tip>
  </Step>

  <Step title="Generate your publishable key">
    Before you can track conversions on the client-side, you need to generate a [publishable key](/docs/api-reference/authentication#publishable-keys) from your Dub workspace.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>
  </Step>

  <Step title="Install Dub Analytics script">
    Next, install the Dub Analytics script on your website/web application.

    You can install the Dub Analytics script in several different ways:

    <CardGroup>
      <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

      <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

      <Card
        title="Framer"
        icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
        href="/docs/sdks/client-side/installation-guides/framer"
      />

      <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

      <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

      <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

      <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
    </CardGroup>

    <Note>
      You must configure the **publishable key** you generated in step 1 when
      installing the analytics script. Without this key, client-side conversion
      tracking will not work.
    </Note>

    <CodeGroup>
      ```html HTML theme={null}
      <script>
        !(function (c, n) {
          c[n] =
            c[n] ||
            function () {
              (c[n].q = c[n].q || []).push(arguments);
            };
          ["trackClick", "trackLead", "trackSale"].forEach(
            (t) => (c[n][t] = (...a) => c[n](t, ...a)),
          );
          var s = document.createElement("script");
          s.defer = 1;
          s.src = "https://www.dubcdn.com/analytics/script.conversion-tracking.js";
          s.setAttribute("data-publishable-key", "dub_pk_xxxxxxxx"); // Replace with your publishable key
          document.head.appendChild(s);
        })(window, "dubAnalytics");
      </script>
      ```

      ```typescript React/Next.js theme={null}
      import { Analytics as DubAnalytics } from '@dub/analytics/react';

      export default function RootLayout({
        children,
      }) {
        return (
          <html lang="en">
            <body className={inter.className}>{children}</body>
            <DubAnalytics
              ...
              publishableKey="dub_pk_xxxxxxxx" // Replace with your publishable key
            />
          </html>
        );
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Step 3: Track lead events

<Tip>
  Looking to track sales without a prior lead event? Check out our [direct sale
  tracking guide](/docs/conversions/sales/direct).
</Tip>

Once the analytics script is installed, you can start tracking lead events in your application on the client-side.

### Track leads from URL query parameters (recommended)

If you redirect users to a thank-you page after a successful action, you can track leads by reading query parameters from the URL.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Thank You</title>
    </head>
    <body>
      <div>Thank you for signing up!</div>

      <script>
        // Get query parameters from URL
        const params = new URLSearchParams(window.location.search);
        const email = params.get("email");
        const name = params.get("name");

        if (email) {
          // Track the lead event
          dubAnalytics.trackLead({
            eventName: "Sign Up",
            customerExternalId: email, // can also be customer email
            customerName: name || undefined,
            customerEmail: email,
          });
        }
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useEffect } from "react";

  export function ThankYouPage() {
    const { trackLead } = useAnalytics();

    useEffect(() => {
      // Get query parameters from URL
      const params = new URLSearchParams(window.location.search);
      const email = params.get("email");
      const name = params.get("name");

      if (email) {
        // Track the lead event
        trackLead({
          eventName: "Sign Up",
          customerExternalId: email, // can also be customer email
          customerName: name || undefined,
          customerEmail: email,
        });
      }
    }, [trackLead]);

    return <div>Thank you for signing up!</div>;
  }
  ```
</CodeGroup>

### Track leads from form submissions

You can also track leads directly when users submit a form on your website.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Sign Up</title>
    </head>
    <body>
      <form id="signupForm">
        <input type="text" id="name" required />
        <input type="email" id="email" required />
        <button type="submit">Sign Up</button>
      </form>

      <script>
        document
          .getElementById("signupForm")
          .addEventListener("submit", function (e) {
            e.preventDefault();

            const name = document.getElementById("name").value;
            const email = document.getElementById("email").value;

            // Track the lead event
            dubAnalytics.trackLead({
              eventName: "Sign Up",
              customerExternalId: email,
              customerName: name,
              customerEmail: email,
            });
          });
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useState } from "react";

  export function SignUpForm() {
    const { trackLead } = useAnalytics();
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const handleSubmit = (e: React.FormEvent) => {
      e.preventDefault();

      // Track the lead event
      trackLead({
        eventName: "Sign Up",
        customerExternalId: email,
        customerName: name,
        customerEmail: email,
      });
    };

    return (
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <button type="submit">Sign Up</button>
      </form>
    );
  }
  ```
</CodeGroup>

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

**When to track leads**

You should track lead events after successful user actions such as:

* User registration or account creation
* Newsletter subscription
* Contact form submission
* Demo request or trial signup
* Download of gated content

Ensure the event is triggered **only after the backend confirms the action was completed successfully**. This guarantees accurate lead data and prevents false or incomplete entries.

## Step 4: Track sale events

Once the analytics script is installed, you can start tracking sale events in your application on the client-side.

### Track sales from URL query parameters (recommended)

If you redirect users to a confirmation page after a successful purchase, you can track sales by reading query parameters from the URL.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Order Confirmation</title>
    </head>
    <body>
      <div>Thank you for your purchase!</div>

      <script>
        // Get query parameters from URL
        const params = new URLSearchParams(window.location.search);
        const customerId = params.get("customer_id");
        const amount = params.get("amount");
        const invoiceId = params.get("invoice_id");

        if (customerId && amount) {
          // Track the sale event
          dubAnalytics.trackSale({
            eventName: "Purchase",
            customerExternalId: customerId, // can also be customer email
            amount: parseInt(amount), // Amount in cents
            invoiceId: invoiceId || undefined,

            // Additional props for direct sale tracking (without prior lead event):
            // clickId: "cm3w...", // Read from dub_id cookie
            // customerName: "John Doe",
            // customerEmail: "john@example.com",
            // customerAvatar: "https://example.com/avatar.jpg",
            // currency: "usd",
            // paymentProcessor: "stripe",
            // metadata: { plan: "pro" },
          });
        }
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useEffect } from "react";

  export function OrderConfirmationPage() {
    const { trackSale } = useAnalytics();

    useEffect(() => {
      // Get query parameters from URL
      const params = new URLSearchParams(window.location.search);
      const customerId = params.get("customer_id");
      const amount = params.get("amount");
      const invoiceId = params.get("invoice_id");

      if (customerId && amount) {
        // Track the sale event
        trackSale({
          eventName: "Purchase",
          customerExternalId: customerId, // can also be customer email
          amount: parseInt(amount), // Amount in cents
          invoiceId: invoiceId || undefined,

          // Additional props for direct sale tracking (without prior lead event):
          // clickId: "cm3w...", // Read from dub_id cookie
          // customerName: "John Doe",
          // customerEmail: "john@example.com",
          // customerAvatar: "https://example.com/avatar.jpg",
        });
      }
    }, [trackSale]);

    return <div>Thank you for your purchase!</div>;
  }
  ```
</CodeGroup>

### Track sales from form submissions

You can also track sales directly when users complete a checkout form on your website.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Checkout</title>
    </head>
    <body>
      <form id="checkoutForm">
        ...
        <button type="submit">Checkout</button>
      </form>

      <script>
        document
          .getElementById("checkoutForm")
          .addEventListener("submit", function (e) {
            e.preventDefault();

            // Track the sale event
            dubAnalytics.trackSale({
              eventName: "Purchase",
              customerExternalId: "cus_RBfbD57H", // can also be customer email
              amount: 5000, // $50.00
              invoiceId: "in_1MtHbELkdIwH",

              // Additional props for direct sale tracking (without prior lead event):
              // clickId: "cm3w...", // Read from dub_id cookie
              // customerName: "John Doe",
              // customerEmail: "john@example.com",
              // customerAvatar: "https://example.com/avatar.jpg",
              // currency: "usd",
              // paymentProcessor: "stripe",
              // metadata: { plan: "pro" },
            });
          });
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useState } from "react";

  export function CheckoutForm() {
    const { trackSale } = useAnalytics();
    // …
  }
    const handleSubmit = (e: React.FormEvent) => {
      e.preventDefault();

      // Track the sale event
      trackSale({
        eventName: "Purchase",
        customerExternalId: "cus_RBfbD57H", // can also be customer email
        amount: 5000, // $50.00
        invoiceId: "in_1MtHbELkdIwH",

        // For direct sale tracking (without prior lead event):
        // clickId: "cm3w...", // Read from dub_id cookie
        // customerName: "John Doe",
        // customerEmail: "john@example.com",
        // customerAvatar: "https://example.com/avatar.jpg",
      });
    };

    return (
      <form onSubmit={handleSubmit}>
        ...
      </form>
    );
  }
  ```
</CodeGroup>

Here are the properties you can include when sending a sale event:

| Property             | Required | Description                                                                                                                                                |
| :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
| `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
| `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
| `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
| `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
| `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
| `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
| `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
| `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
| `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
| `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |

**When to track sale**

Track sale events only after a user successfully completes a purchase or payment-related action, such as:

* Completing a checkout or order
* Subscription payment
* Invoice payment
* Any paid trial or demo conversion

Ensure the event is triggered **only after the backend confirms the payment was successful**. This guarantees accurate sale data and prevents false or incomplete entries.

<Tip>
  Looking to track refunds? Check out our [refunds tracking
  guide](/docs/conversions/sales/refunds).
</Tip>

## Step 5: View your conversions

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>


# Server-side tracking
Source: https://dub.co/docs/quickstart/server

Learn how to track conversion events with Dub using server-side tracking.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's powerful [attribution platform](/docs/concepts/attribution) lets you understand how well your links are translating to actual users and revenue dollars.

<Frame>
  <img alt="Conversion analytics" />
</Frame>

In this guide, we'll learn how to track conversion events with Dub using server-side tracking.

<Note>
  The recommended way to track conversions on Dub is using server-side tracking, which is more reliable than [client-side tracking](/docs/quickstart/client).
</Note>

## Step 1: Enable conversion tracking for your links

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Step 2: Install the Dub Analytics script

Next, you'll need to install the [Dub Analytics script](/docs/sdks/client-side/introduction).

This script detects the `dub_id` query parameter and storing it as a first-party cookie, which will be used to attribute subsequent conversion events to the original link.

<Frame>
  <img alt="A diagram showing how click events are tracked in the conversion funnel" />
</Frame>

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Step 3: Install the Dub server-side SDK

Depending on which framework you're using, you can use our [native SDKs](/docs/sdks/overview) to track conversion events:

<CardGroup>
  <Card title="TypeScript" icon="npm" href="/docs/sdks/typescript">
    TypeScript library for the Dub API
  </Card>

  <Card title="Go" icon="golang" href="/docs/sdks/go">
    Go library for the Dub API
  </Card>

  <Card title="Python" icon="python" href="/docs/sdks/python">
    Python library for the Dub API
  </Card>

  <Card title="Ruby" icon="gem" href="/docs/sdks/ruby">
    Ruby library for the Dub API
  </Card>

  <Card title="PHP" icon="php" href="/docs/sdks/php">
    PHP library for the Dub API
  </Card>
</CardGroup>

If you're using a framework that isn't listed, you can use the Dub REST API to track events on the server-side:

* [`POST /track/lead`](/docs/api-reference/track/lead)
* [`POST /track/sale`](/docs/api-reference/track/sale)

## Step 4: Track conversion events

Now comes the fun part – tracking conversion events.

### Tracking lead events

The first event you'll want to track is a `lead` event. This happens when a user performs an action that indicates interest in your product or service. This could be anything from:

* Signing up for an account
* Adding a product to cart
* Joining a mailing list

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

Our most common lead event is `Sign Up`, which happens when a user signs up for an account. Depending on which authentication framework you're using, here are a few examples of how to send `Sign Up` lead events:

<CardGroup>
  <Card title="Clerk" icon="shield-halved" href="https://dub.co/docs/conversions/leads/clerk" />

  <Card title="Better Auth" icon="shield-halved" href="https://dub.co/docs/conversions/leads/better-auth" />

  <Card title="NextAuth.js" icon="shield-halved" href="https://dub.co/docs/conversions/leads/next-auth" />

  <Card title="Supabase" icon="shield-halved" href="https://dub.co/docs/conversions/leads/supabase" />

  <Card title="Auth0" icon="shield-halved" href="https://dub.co/docs/conversions/leads/auth0" />

  <Card title="Appwrite" icon="shield-halved" href="https://dub.co/docs/conversions/leads/appwrite" />
</CardGroup>

The lead event will serve as the source of truth for the customer's identity and which link they came from. This means that all subsequent actions performed by the customer (e.g. upgrading their plan, purchasing a product) will automatically be attributed to the original link.

### Tracking sale events

The second event you'll want to send is a `sale` event. This happens when a user purchases your product or service. This could be anything from:

* Subscribing to a paid plan
* Usage expansion (upgrading from one plan to another)
* Purchasing a product

<Frame>
  <img alt="A diagram showing how sale events are tracked in the conversion funnel" />
</Frame>

Depending on which payment processor you're using, we offer native integrations for the following:

<CardGroup>
  <Card title="Stripe" icon="stripe" href="https://dub.co/docs/integrations/stripe" />

  <Card title="Shopify" icon="shopify" href="https://dub.co/docs/integrations/stripe" />
</CardGroup>

Alternatively, you can also send sale events manually using [our SDKs](/docs/sdks/overview) or the [`POST /track/sale` API endpoint](/docs/api-reference/track/sale).

<Tip>
  Looking to track sales without a prior lead event? Check out our [direct sale
  tracking guide](/docs/conversions/sales/direct).

  Looking to track refunds? Check out our [refunds tracking guide](/docs/conversions/sales/refunds).
</Tip>

## Step 5: View your conversions

Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://app.dub.co/analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](https://app.dub.co/events) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## Example Apps

<CardGroup>
  <Card title="Dub conversion tracking demo" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/stripe">
    Tracking Stripe sale events with Dub.
  </Card>

  <Card title="Segment + Dub demo app" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/segment">
    Tracking conversion events with Segment and Dub.
  </Card>

  <Card title="Clerk + Dub demo app" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/clerk">
    Tracking Clerk signup events with Dub.
  </Card>

  <Card title="Cal.com + Dub demo app" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions/cal">
    Tracking Cal.com booking events with Dub.
  </Card>
</CardGroup>


# React Native
Source: https://dub.co/docs/sdks/client-side-mobile/installation-guides/react-native

How to add the Dub React Native SDK to your React Native project

## Prerequisites

Before you get started, make sure you have the following:

1. Obtain your [publishable key](/docs/api-reference/authentication#publishable-keys) (`DUB_PUBLISHABLE_KEY`) from your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
2. You would also want to have your [custom domain](https://app.dub.co/settings/domains) (`DUB_DOMAIN`) handy as well.
3. (Optional) If you plan to track conversions, make sure to [enable conversion tracking for your links](/docs/quickstart/server#step-1-enable-conversion-tracking-for-your-links).

## Quickstart

This quick start guide will show you how to get started with Dub React Native SDK in your React Native app.

<Steps>
  <Step title="Install the Dub React Native SDK">
    ```sh theme={null}
    # With npm
    npm install @dub/react-native

    # With yarn
    yarn add @dub/react-native

    # With pnpm
    pnpm add @dub/react-native
    ```
  </Step>

  <Step title="Initialize the SDK">
    You must call `init` on your `dub` instance with your publishable key and domain prior to being able to use the `dub` instance. We provide two ways to initialize the SDK:

    **Option 1**: Use the `DubProvider` to wrap your app

    ```typescript theme={null}
    import { DubProvider } from "@dub/react-native";

    export default function App() {
      return (
        <DubProvider
          publishableKey="<DUB_PUBLISHABLE_KEY>"
          dubDomain="<DUB_DOMAIN>"
        >
          // Your app content...
        </DubProvider>
      );
    }
    ```

    **Option 2**: Manually initialize the Dub SDK

    ```typescript theme={null}
    import dub from "@dub/react-native";

    export default function App() {
      useEffect(() => {
        dub.init({
          publishableKey: "<DUB_PUBLISHABLE_KEY>",
          domain: "<DUB_DOMAIN>",
        });
      }, []);

      // Return your app...
    }
    ```
  </Step>

  <Step title="Track deep link open events">
    Call `trackOpen` on the `dub` instance to track deep link and deferred deep link open events.
    The `trackOpen` function should be called once without a `deepLink` parameter on first launch, and then
    again with the `deepLink` parameter whenever the app is opened from a deep link.

    <Warning>
      If your iOS app uses third-party SDKs that handle URLs (e.g., Facebook/Meta SDK), make sure your `AppDelegate`'s `openURL:` handler doesn't short-circuit with `||`. When a third-party SDK returns `YES` first, `RCTLinkingManager` never fires and `trackOpen` won't be called. See the [troubleshooting guide](/docs/concepts/deep-links/quickstart#troubleshooting-deep-links) for details and a fix.
    </Warning>

    ```typescript React Native expandable theme={null}
    import { useState, useEffect, useRef } from "react";
    import { Linking } from "react-native";
    import AsyncStorage from "@react-native-async-storage/async-storage";
    import dub from "@dub/react-native";

    export default function App() {
      useEffect(() => {
        dub.init({
          publishableKey: "<DUB_PUBLISHABLE_KEY>",
          domain: "<DUB_DOMAIN>",
        });

        // Check if this is first launch
        const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

        if (isFirstLaunch === null) {
          await handleFirstLaunch();
          await AsyncStorage.setItem("is_first_launch", "false");
        } else {
          // Handle initial deep link url (Android only)
          const url = await Linking.getInitialURL();

          if (url) {
            await handleDeepLink(url);
          }
        }

        const linkingListener = Linking.addEventListener("url", (event) => {
          handleDeepLink(event.url);
        });

        return () => {
          linkingListener.remove();
        };
      }, []);

      const handleFirstLaunch = async (
        deepLinkUrl?: string | null | undefined
      ): Promise<void> => {
        try {
          const response = await dub.trackOpen(deepLinkUrl);

          const destinationURL = response.link?.url;
          // Navigate to the destination URL
        } catch (error) {
          // Handle error
        }
      };

      // Return your app...
    }
    ```
  </Step>

  <Step title="Track lead events (optional)">
    To track lead events, call `trackLead` on the `dub` instance with your customer's external ID, name, and email.

    ```typescript React Native theme={null}
    import dub from "@dub/react-native";

    try {
      await dub.trackLead({
        eventName: "User Sign Up",
        customerExternalId: user.id,
        customerName: user.name,
        customerEmail: user.email,
      });
    } catch (error) {
      // Handle sale tracking error
    }
    ```

    <Accordion title="Lead event attributes">
      Here are the properties you can include when sending a lead event:

      | Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
      | :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
      | `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
      | `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
      | `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
      | `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
      | `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
      | `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
      | `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |
    </Accordion>
  </Step>

  <Step title="Track sale events (optional)">
    To track sale events, call `trackSale` on the `dub` instance with your customer's user ID and purchase information.

    ```typescript React Native theme={null}
    import dub from "@dub/react-native";

    try {
      await dub.trackSale({
        customerExternalId: user.id,
        amount: product.price.amount,
        currency: "usd",
        eventName: "Purchase",
      });
    } catch (error) {
      // Handle sale tracking error
    }
    ```

    <Accordion title="Sale event attributes">
      Here are the properties you can include when sending a sale event:

      | Property             | Required | Description                                                                                                                                                |
      | :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
      | `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
      | `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
      | `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
      | `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
      | `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
      | `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
      | `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
      | `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
      | `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
      | `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |
    </Accordion>
  </Step>
</Steps>

## Examples

Here are some open-source code examples that you can reference:

<CardGroup>
  <Card title="React Native" icon="github" href="https://github.com/dubinc/dub-react-native/tree/main/example">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Swift
Source: https://dub.co/docs/sdks/client-side-mobile/installation-guides/swift

How to add the Dub iOS SDK to your Swift project

## Prerequisites

Before you get started, make sure you have the following:

1. Obtain your [publishable key](/docs/api-reference/authentication#publishable-keys) (`DUB_PUBLISHABLE_KEY`) from your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
2. You would also want to have your [custom domain](https://app.dub.co/settings/domains) (`DUB_DOMAIN`) handy as well.
3. (Optional) If you plan to track conversions, make sure to [enable conversion tracking for your links](/docs/quickstart/server#step-1-enable-conversion-tracking-for-your-links).

## Quickstart

This quick start guide will show you how to get started with Dub iOS SDK in your Swift project.

<Steps>
  <Step title="Install the Dub iOS SDK">
    Before installing, ensure your environment meets these minimum requirements:

    **Build Tools:**

    * Xcode 16+
    * Swift 4.0+

    **Platforms:**

    * iOS 16.0+
    * macOS 10.13 (Ventura)+

    The Dub iOS SDK can be installed using the [Swift Package Manager](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/).

    In Xcode, select **File** > **Add Package Dependencies** and add `https://github.com/dubinc/dub-ios` as the repository URL. Select the latest version of the SDK from the [release page](https://github.com/dubinc/dub-ios/releases).
  </Step>

  <Step title="Initialize the SDK">
    You must call `Dub.setup` with your publishable key and domain prior to being able to use the `dub` instance.

    <CodeGroup>
      ```swift iOS (SwiftUI) theme={null}
      import SwiftUI
      import Dub

      @main
      struct DubApp: App {
          // Step 1: Obtain your Dub domain and publishable key
          private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
          private let dubDomain = "<DUB_DOMAIN>"

          init() {
              // Step 2: Initialize the Dub SDK by calling `setup`
              Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)
          }

          var body: some Scene {
              WindowGroup {
                  ContentView()
                      // Step 3: Expose the `dub` instance as a SwiftUI environment value
                      .environment(\.dub, Dub.shared)
              }
          }
      }
      ```

      ```swift iOS (UIKit) theme={null}
      import UIKit
      import Dub

      @main
      class AppDelegate: UIResponder, UIApplicationDelegate {

          var window: UIWindow?

          // Step 1: Obtain your Dub domain and publishable key
          private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
          private let dubDomain = "<DUB_DOMAIN>"

          func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
              // Step 2: Initialize the Dub SDK by calling `setup`
              Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)
              return true
          }
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Track deep link open events">
    Call `trackOpen` on the `dub` instance to track deep link and deferred deep link open events.
    The `trackOpen` function should be called once without a `deepLink` parameter on first launch, and then
    again with the `deepLink` parameter whenever the app is opened from a deep link.

    <Warning>
      **UIKit apps:** If your `AppDelegate` has multiple URL handlers chained with `||` (e.g., Facebook/Meta SDK alongside Dub), the short-circuit evaluation may prevent `handleDeepLink` and `trackOpen` from being called. Make sure all handlers execute independently. See the [troubleshooting guide](/docs/concepts/deep-links/quickstart#troubleshooting-deep-links) for details and a fix.
    </Warning>

    <CodeGroup>
      ```swift iOS (SwiftUI) expandable theme={null}
      // ContentView.swift
      import SwiftUI
      import Dub

      struct ContentView: View {

          @Environment(\.dub) var dub: Dub

          @AppStorage("is_first_launch") private var isFirstLaunch = true

          var body: some View {
              NavigationStack {
                  VStack {
                      // Your app content
                  }
                  .onOpenURL { url in
                      trackOpen(deepLink: url)
                  }
                  .onAppear {
                      if isFirstLaunch {
                          trackOpen()
                          isFirstLaunch = false
                      }
                  }
              }
          }

          private func trackOpen(deepLink: URL? = nil) {
              Task {
                  do {
                      let response = try await dub.trackOpen(deepLink: deepLink)

                      // Obtain the destination URL from the response
                      guard let url = response.link?.url else {
                          return
                      }

                      // Navigate to the destination URL
                  } catch let error as DubError {
                      print(error.localizedDescription)
                  }
              }
          }
      }
      ```

      ```swift iOS (UIKit) expandable theme={null}
      import UIKit
      import Dub

      @main
      class AppDelegate: UIResponder, UIApplicationDelegate {

          var window: UIWindow?

          private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
          private let dubDomain = "<DUB_DOMAIN>"

          private var isFirstLaunch: Bool {
              get {
                  UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
              }
              set {
                  UserDefaults.standard.set(newValue, forKey: "is_first_launch")
              }
          }

          func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
              Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

              // Track first launch
              if isFirstLaunch {
                  trackOpen()
                  isFirstLaunch = false
              }

              // Override point for customization after application launch.
              return true
          }

          func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
              handleDeepLink(url: url)
              return true
          }

          func handleDeepLink(url: URL) {
              trackOpen(deepLink: url)
          }

          private func trackOpen(deepLink: URL? = nil) {
              // Call the tracking endpoint with the full deep link URL
              Task {
                  do {
                      let response = try await Dub.shared.trackOpen(deepLink: deepLink)

                      print(response)

                      // Navigate to final link via link.url
                      guard let destinationUrl = response.link?.url else {
                          return
                      }

                      // Navigate to the destination URL
                  } catch let error as DubError {
                      print(error.localizedDescription)
                  }
              }
          }
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Track lead events (optional)">
    To track lead events, call `trackLead` on the `dub` instance with your customer's external ID, name, and email.

    <CodeGroup>
      ```swift iOS (SwiftUI) expandable theme={null}
      // ContentView.swift
      import SwiftUI
      import Dub

      struct ContentView: View {

          @Environment(\.dub) var dub: Dub

          var body: some View {
             // ... your app content ...
          }

          private func trackLead(customerExternalId: String, name: String, email: String) {
              Task {
                  do {
                      let response = try await dub.trackLead(
                          eventName: "User Sign Up",
                          customerExternalId: customerExternalId,
                          customerName: name,
                          customerEmail: email
                      )

                      print(response)
                  } catch let error as DubError {
                      print(error.localizedDescription)
                  }
              }
          }
      }
      ```

      ```swift iOS (UIKit) expandable theme={null}
      // ViewController.swift
      import UIKit
      import Dub

      class ViewController: UIViewController {
          // View controller lifecycle...

          private func trackLead(customerExternalId: String, name: String, email: String) {
              Task {
                  do {
                      let response = try await Dub.shared.trackLead(
                          eventName: "User Sign Up",
                          customerExternalId: customerExternalId,
                          customerName: name,
                          customerEmail: email
                      )

                      print(response)
                  } catch let error as DubError {
                      print(error.localizedDescription)
                  }
              }
          }
      }
      ```
    </CodeGroup>

    <Accordion title="Lead event attributes">
      Here are the properties you can include when sending a lead event:

      | Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
      | :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
      | `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
      | `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
      | `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
      | `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
      | `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
      | `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
      | `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |
    </Accordion>
  </Step>

  <Step title="Track sale events (optional)">
    To track sale events, call `trackSale` on the `dub` instance with your customer's user ID and purchase information.

    <CodeGroup>
      ```swift iOS (SwiftUI) expandable theme={null}
      // ContentView.swift
      import SwiftUI
      import Dub

      struct ContentView: View {

        @Environment(\.dub) var dub: Dub

        var body: some View {
           // ... your app content ...
        }

        private func trackSale(
            customerExternalId: String,
            amount: Int,
            currency: String = "usd",
            eventName: String? = "Purchase",
            paymentProcessor: PaymentProcessor = .custom,
            invoiceId: String? = nil,
            metadata: Metadata? = nil,
            leadEventName: String? = nil,
            customerName: String? = nil,
            customerEmail: String? = nil,
            customerAvatar: String? = nil
        ) {
            Task {
                do {
                    let response = try await dub.trackSale(
                        customerExternalId: customerExternalId,
                        amount: amount,
                        currency: currency,
                        eventName: eventName,
                        paymentProcessor: paymentProcessor,
                        invoiceId: invoiceId,
                        metadata: metadata,
                        leadEventName: leadEventName,
                        customerName: customerName,
                        customerEmail: customerEmail,
                        customerAvatar: customerAvatar
                    )

                    print(response)
                } catch let error as DubError {
                    print(error.localizedDescription)
                }
            }
        }
      }
      ```

      ```swift iOS (UIKit) expandable theme={null}
      // ViewController.swift
      import UIKit
      import Dub

      class ViewController: UIViewController {
          // View controller lifecycle...

        private func trackSale(
            customerExternalId: String,
            amount: Int,
            currency: String = "usd",
            eventName: String? = "Purchase",
            paymentProcessor: PaymentProcessor = .custom,
            invoiceId: String? = nil,
            metadata: Metadata? = nil,
            leadEventName: String? = nil,
            customerName: String? = nil,
            customerEmail: String? = nil,
            customerAvatar: String? = nil
        ) {
            Task {
                do {
                    let response = try await Dub.shared.trackSale(
                        customerExternalId: customerExternalId,
                        amount: amount,
                        currency: currency,
                        eventName: eventName,
                        paymentProcessor: paymentProcessor,
                        invoiceId: invoiceId,
                        metadata: metadata,
                        leadEventName: leadEventName,
                        customerName: customerName,
                        customerEmail: customerEmail,
                        customerAvatar: customerAvatar
                    )

                    print(response)
                } catch let error as DubError {
                    print(error.localizedDescription)
                }
            }
        }
      }
      ```
    </CodeGroup>

    <Accordion title="Sale event attributes">
      Here are the properties you can include when sending a sale event:

      | Property             | Required | Description                                                                                                                                                |
      | :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
      | `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
      | `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
      | `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
      | `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
      | `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
      | `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
      | `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
      | `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
      | `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
      | `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |
    </Accordion>
  </Step>
</Steps>

## Examples

Here are some open-source code examples that you can reference:

<CardGroup>
  <Card title="Swift (SwiftUI)" icon="github" href="https://github.com/dubinc/dub-ios/tree/main/Examples/SwiftUI">
    See the full example on GitHub.
  </Card>

  <Card title="Swift (UIKit)" icon="github" href="https://github.com/dubinc/dub-ios/tree/main/Examples/UIKit">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Introduction
Source: https://dub.co/docs/sdks/client-side-mobile/introduction

Learn more about the the Dub client-side mobile SDKs.

Dub offers first-party, open-source, mobile SDKs to help you track [deep links](/docs/concepts/deep-links/quickstart), [deferred deep links](/docs/concepts/deep-links/deferred-deep-linking), and [deep link attribution](/docs/concepts/deep-links/attribution) in your mobile applications.

Currently, we support the following mobile SDKs:

* [Dub iOS SDK (beta)](https://github.com/dubinc/dub-ios)
* [Dub React Native SDK (beta)](https://github.com/dubinc/dub-react-native)

<Note>
  Android support is coming soon. If you'd like early access, please [contact
  us](https://dub.co/contact/support).
</Note>

## Installation guides

Dub currently supports the following client-side mobile SDKs:

<CardGroup>
  <Card title="Swift" icon="swift" href="/docs/sdks/client-side-mobile/installation-guides/swift">
    Add the Dub iOS SDK to your app (Swift)
  </Card>

  <Card title="React Native" icon="react" href="/docs/sdks/client-side-mobile/installation-guides/react-native">
    Add the Dub React Native SDK to your app (React Native)
  </Card>
</CardGroup>

## Open-source examples

Here are some open-source code examples that you can reference:

<CardGroup>
  <Card title="Swift (SwiftUI)" icon="github" href="https://github.com/dubinc/dub-ios/tree/main/Examples/SwiftUI">
    See the full example on GitHub.
  </Card>

  <Card title="Swift (UIKit)" icon="github" href="https://github.com/dubinc/dub-ios/tree/main/Examples/UIKit">
    See the full example on GitHub.
  </Card>

  <Card title="React Native" icon="github" href="https://github.com/dubinc/dub-react-native/tree/main/example">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Client-side click tracking
Source: https://dub.co/docs/sdks/client-side/features/click-tracking

Track clicks on the client-side using query parameters

With the [Dub Analytics script](/docs/sdks/client-side/introduction), you can track clicks on the client-side using query parameters (e.g. `?via=john`, `?ref=jane`).

A few use cases include:

* You're using [Dub Partners](/help/article/dub-partners) with [dual-sided incentives](/help/article/dual-sided-incentives) and want to display a banner that says: *"John referred you to Acme and gave you 25% off"*
* You are migrating from an existing affiliate management platform (e.g. [Rewardful](/help/article/migrating-from-rewardful)) that uses query parameters to track conversions.
* You are running an ad on a platform like Google/Reddit that requires you to use your main site domain for the URL (no short links allowed) – so instead of using a short link, you can use a query parameter to track clicks.
* You have dynamically generated referral pages (e.g. [Tesla](https://www.tesla.com/referral/peeroke520149)) and want to track clicks [using a `trackClick()` function](#manually-tracking-clicks-with-the-trackclick-function) inside your application code.

## Quickstart

Here's how you can enable client-side click-tracking with the Dub Analytics script:

<Steps>
  <Step title="Add a custom domain to your Dub workspace">
    First, you'll need to [add a custom short link domain](/help/article/how-to-add-custom-domain) to your Dub workspace. You can use a domain you already own, or leverage our [free .link domain offer](/help/article/free-dot-link-domain) to register a custom domain like `yourcompany.link` for free.

    This is the domain that you'll use for your short links on Dub.
  </Step>

  <Step title="Allowlist your site's domain">
    Then, you'll need to allowlist your site's domain to allow the client-side click events to be ingested by Dub. To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and add your site's domain to the **Allowed Hostnames** list.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    You can group your hostnames when adding them to the allow list:

    * `example.com`: Tracks traffic **only** from `example.com`.
    * `*.example.com`: Tracks traffic from **all subdomains** of `example.com`, but **not** from `example.com` itself.

    <Tip>
      When testing things out locally, you can add `localhost` to the **Allowed
      Hostnames** list temporarily. This will allow local events to be ingested by
      Dub. Don't forget to remove it once you're ready to go live!
    </Tip>
  </Step>

  <Step title="Install the Dub client-side SDK">
    Next, install the Dub [client-side SDK](/docs/sdks/client-side/introduction) and initialize it with the domain you added in the previous step.

    <CodeGroup>
      ```javascript HTML theme={null}
      // include this script tag in your HTML Head tag
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        data-domains='{"refer": "go.example.com"}'
        defer
      ></script>
      ```

      ```typescript React/Next.js theme={null}
      // install the package (e.g. npm install @dub/analytics)
      import { Analytics as DubAnalytics } from "@dub/analytics/react";

      export default function App() {
        return (
          <Layout>
            <DubAnalytics
              domainsConfig={{
                refer: "go.example.com", // the custom domain you're using on Dub for your short links
              }}
            />
            {/* Your app code here */}
          </Layout>
        );
      }
      ```
    </CodeGroup>

    Here's the full list of parameters you can pass to the script:

    <Accordion title="Parameters">
      <ParamField type="url">
        The base URL for the Dub API. This is useful for [setting up reverse
        proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
        adblockers.
      </ParamField>

      <ParamField type="first-click | last-click">
        The attribution model to use for the analytics event. The following
        attribution models are available:

        * `first-click`: The first click model
          gives all the credit to the first touchpoint in the customer journey.
        * `last-click`: The last click model gives all the credit to the last
          touchpoint in the customer journey.
      </ParamField>

      <ParamField type="CookieOption Object">
        <Expandable title="properties">
          <ParamField type="string">
            Specifies the value for the `Domain` Set-Cookie attribute. This is useful
            for cross-domain tracking. Example: `.example.com`
          </ParamField>

          <ParamField type="integer">
            Specifies the `Date` object to be the value for the `Expires` Set-Cookie
            attribute. Example: `new Date('2024-12-31')`
          </ParamField>

          <ParamField type="integer">
            Specifies the number (in days) to be the value for the `Expires`
            Set-Cookie attribute. Example: `90`
          </ParamField>

          <ParamField type="string">
            Specifies the value for the `Path` Set-Cookie attribute. By default, the
            path is considered the "default path". Example: `/`
          </ParamField>
        </Expandable>
      </ParamField>

      <ParamField type="JSON-stringified object">
        Configure the domains that Dub will track. The following properties are available:

        <Expandable title="properties">
          <ParamField type="string">
            The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer)
            (previously `shortDomain`).
            Example: `refer.dub.co`
          </ParamField>

          <ParamField type="string">
            The Dub short domain for tracking site visits.
            Example: `site.dub.co`
          </ParamField>

          <ParamField type="string | string[]">
            An array of domains for cross-domain tracking. When configured, the existing
            `dub_id` cookie will be automatically appended to all outbound links
            targeting these domains to enable cross-domain tracking across different
            applications.
            Example: `["dub.sh", "git.new"]`
          </ParamField>
        </Expandable>
      </ParamField>

      <ParamField type="string[]">
        An array of query parameters to listen to for client-side click-tracking (e.g.
        `?via=abc123`).
      </ParamField>

      <ParamField type="HTMLScriptElement Object">
        Custom properties to pass to the script tag. Refer to
        [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement) for
        all available options.
      </ParamField>
    </Accordion>
  </Step>

  <Step title="(Optional, but recommended): Set up a reverse proxy">
    To avoid ad-blockers from blocking your click-tracking requests, we recommend setting up a reverse proxy.

    Refer to the [Reverse-proxy support](/docs/sdks/client-side/features/reverse-proxy-support) guide to learn how to set one up.
  </Step>

  <Step title="Verify your setup">
    To verify that your click-tracking is working, run your website locally and append the URL with the unique key of your short link

    For example, if your short link is `https://go.example.com/abc123`, you'll need to append `?via=abc123` to the URL.

    Once you've done that, check if the following is true:

    1. There is a successful `/track/click` request in your browser's **Network** tab (and no errors in the **Console** tab).
    2. The `dub_id` cookie is being set upon a successful `/track/click` request.
    3. The click tracked correctly in the [**Events**](https://app.dub.co/events) tab of your Dub workspace.
  </Step>
</Steps>

## Automatically fetching partner and discount data

If you're using [Dub Partners](https://dub.co/partners) with [dual-sided incentives](/help/article/dual-sided-incentives), the script will automatically fetch the partner and discount data for you when someone lands on your site via a valid referral link.

This data will be stored as a JSON-stringified object in the `dub_partner_data` cookie in the following format:

```json theme={null}
{
  "clickId": "xxx", // unique ID of the click event
  "partner": {
    "id": "pn_xxx", // unique ID of the partner on Dub
    "name": "John Doe", // name of the partner
    "image": "https://example.com/john.png", // avatar of the partner
    "groupId": "grp_xxx", // unique ID of the group the partner belongs to
    "tenantId": "user_xxx" // partner's unique ID in your database
  },
  "discount": {
    "id": "disc_xxx", // unique ID of the discount on Dub
    "amount": 25, // discount amount (either a percentage or a fixed amount)
    "type": "percentage", // type of the discount (either "percentage" or "fixed")
    "maxDuration": 3, // maximum duration of the discount in months
    "couponId": "XZuejd0Q", // Stripe coupon code
    "couponTestId": "2NMXz81x" // Stripe test coupon ID
  }
}
```

You can access partner and discount data in your application code using the `useAnalytics()` hook. If you’re working in a non-React environment, you can use the `DubAnalytics` object directly.

Here is a quick example of how you can display a discount banner using the `useAnalytics()` hook:

<CodeGroup>
  ```javascript HTML theme={null}
  // Display a banner that says: _"John referred you to Acme and gave you 25% off"_
  dubAnalytics("ready", function () {
    if (DubAnalytics.partner && DubAnalytics.discount) {
      const banner = document.createElement("div");

      banner.innerHTML = `
        <div class="flex items-center gap-2">
          <img src="${DubAnalytics.partner.image}" alt="${DubAnalytics.partner.name}" class="size-6 rounded-full" />
          <p>${DubAnalytics.partner.name} referred you to Acme and gave you ${DubAnalytics.discount.amount} ${DubAnalytics.discount.type} off</p>
        </div>
      `;
      document.body.appendChild(banner);
    }
  });
  ```

  ```typescript React/Next.js theme={null}
  // Display a banner that says: _"John referred you to Acme and gave you 25% off"_
  import { useAnalytics } from "@dub/analytics/react";

  function DiscountBanner() {
    const { partner, discount } = useAnalytics();

    if (!partner || !discount) {
      return null;
    }

    return (
      <div className="flex items-center gap-2">
        <img
          src={partner.image}
          alt={partner.name}
          className="size-6 rounded-full"
        />
        <p>
          {partner.name} referred you to Acme and gave you {discount.amount}{" "}
          {discount.type} off
        </p>
      </div>
    );
  }
  ```
</CodeGroup>

Here's an example of how the discount banner will look like:

<Frame>
  <img alt="A screenshot of the Dub partner data example" />
</Frame>

## Manually tracking clicks with the `trackClick()` function

This is helpful for tracking clicks on:

* Dynamically generated referral pages (e.g. [Tesla](https://www.tesla.com/referral/peeroke520149))
* Dynamic user-generated content/webpages:
  * Dub's public analytics dashboards (e.g. [app.dub.co/share/:slug](https://app.dub.co/share/dash_6NSA6vNm017MZwfzt8SubNSZ))
  * v0 template pages (e.g. [v0.app/templates/:slug](https://v0.app/templates/evasion-e-commerce-template-annqMtFCROP))
  * Tella's video pages (e.g. [tella.tv/video/:slug](https://www.tella.tv/video/cluvcfcfi00tw0fjrgizr4pw2))

The `trackClick()` function allows you to manually trigger click events from your application code. This is useful when you want to track clicks that happen programmatically.

The `trackClick()` function accepts an object with the following parameters:

<ParamField type="string">
  The domain of the short link (e.g. `getacme.link`)
</ParamField>

<ParamField type="string">
  The short link slug (e.g. `john`)
</ParamField>

Here's how you can use the `trackClick()` function in your application:

<CodeGroup>
  ```javascript HTML theme={null}
  document.addEventListener("DOMContentLoaded", function () {
    // Extract the key from the URL path (e.g., /share/dash_6NSA6vNm017MZwfzt8SubNSZ -> dash_6NSA6vNm017MZwfzt8SubNSZ)
    const pathSegments = window.location.pathname.split("/");
    const key = pathSegments[2]; // e.g. dash_6NSA6vNm017MZwfzt8SubNSZ

    if (key) {
      dubAnalytics.trackClick({
        domain: "getacme.link",
        key,
      });
    }
  });

  // This will track clicks for URLs like app.dub.co/share/dash_6NSA6vNm017MZwfzt8SubNSZ
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useRouter } from "next/router";

  function BookingPage() {
    const router = useRouter();
    const { trackClick } = useAnalytics();

    // Extract the key from the URL path (e.g., /john -> john)
    const { slug: key } = router.query;

    useEffect(() => {
      if (key) {
        trackClick({
          domain: "getacme.link",
          key,
        });
      }
    }, [key, trackClick]);

    return <></>;
  }
  ```
</CodeGroup>

## Differences from server-side click-tracking

Server-side click-tracking is enabled by default for all Dub links and come with the following attributes:

| Attribute    | Type    | Description                                                                                      |
| :----------- | :------ | :----------------------------------------------------------------------------------------------- |
| `timestamp`  | string  | The timestamp of the click event                                                                 |
| `id`         | string  | The unique ID of the click event                                                                 |
| `url`        | string  | The destination URL that the link resolved to – this can vary if geo/device-targeting is enabled |
| `continent`  | string  | The continent of the user who clicked the link                                                   |
| `country`    | string  | The country of the user who clicked the link                                                     |
| `city`       | string  | The city of the user who clicked the link                                                        |
| `device`     | string  | The device of the user who clicked the link                                                      |
| `browser`    | string  | The browser of the user who clicked the link                                                     |
| `os`         | string  | The operating system of the user who clicked the link                                            |
| `referer`    | string  | The referrer of the user who clicked the link                                                    |
| `refererUrl` | string  | The full referrer URL of the user who clicked the link                                           |
| `qr`         | boolean | Whether the click event was triggered by a QR code scan                                          |
| `ip`         | string  | The IP address of the user who clicked the link (non-EU users only)                              |

These events happen on the server-side and cannot be blocked by browser extensions or ad-blockers, which improves the accuracy of your analytics data.


# Client-side conversion tracking
Source: https://dub.co/docs/sdks/client-side/features/conversion-tracking

Learn how to use the Dub Analytics script to track conversion events on the client-side

Dub Analytics is a client-side script for [tracking conversion events](/docs/concepts/attribution) with Dub.

By default, the script handles the detection of the `dub_id` query parameter and storing it as a first-party cookie:

<Frame>
  <img alt="A diagram showing how click events are tracked in the conversion funnel" />
</Frame>

Then, when a conversion event occurs (e.g. a user signs up for an account), you can check for the `dub_id` cookie and attribute the conversion to the original click by [tracking a lead event](/docs/quickstart/server#tracking-lead-events).

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

Finally, when the user completes a purchase (e.g. subscribing to a plan, purchasing a product, etc.), you can [track a sale event](/docs/quickstart/server#tracking-sale-events). Under the hood, Dub will automatically attribute the sale to the original link click.

<Frame>
  <img alt="A diagram showing how sale events are tracked in the conversion funnel" />
</Frame>

## Quickstart

First, you'll need to enable conversion tracking for your Dub links to be able to start tracking conversions:

<Tip>
  If you're using [Dub Partners](https://dub.co/partners), you can skip this
  step since partner links will have conversion tracking enabled by default.
</Tip>

<AccordionGroup>
  <Accordion title="Option 1: On a workspace-level">
    To enable conversion tracking for all future links in a workspace, you can do the following:
    To enable conversion tracking for all future links in a workspace, you can do the following:

    1. Navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking).
    2. Toggle the **Workspace-level Conversion Tracking** switch to enable conversion tracking for the workspace.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.
  </Accordion>

  <Accordion title="Option 2: On a link-level">
    If you don't want to enable conversion tracking for all your links in a workspace, you can also opt to enable it on a link-level.

    To enable conversion tracking for a specific link, open the [Dub Link Builder](/help/article/dub-link-builder) for a link and toggle the **Conversion Tracking** switch.

    <Frame>
      <img alt="Enabling conversion tracking for a link" />
    </Frame>

    <Tip>
      You can also use the `C` keyboard shortcut when inside the link builder to
      quickly enable conversion tracking for a given link.
    </Tip>
  </Accordion>

  <Accordion title="Option 3: Via the API">
    Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/docs/api-reference/introduction). All you need to do is pass `trackConversion: true` when creating or updating a link:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const link = await dub.links.create({
        url: "https://dub.co",
        trackConversion: true,
      });
      ```

      ```python Python theme={null}
      link = d.links.create(url="https://dub.co", track_conversion=True)
      ```

      ```go Go theme={null}
      link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
          URL: "https://dub.co",
          TrackConversion: true,
      })
      ```

      ```ruby Ruby theme={null}
      s.links.create_many(
        ::OpenApiSDK::Operations::CreateLinkRequest.new(
          url: "https://dub.co",
          track_conversion: true,
        )
      )
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

***

Then, you'll need to install the Dub Analytics script and set up the necessary configuration for client-side conversion tracking:

<Steps>
  <Step title="Allowlist your site's domain">
    Then, you'll need to allowlist your site's domain to allow the client-side conversion events to be ingested by Dub.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and add your site's domain to the **Allowed Hostnames** list.

    This provides an additional layer of security by ensuring only authorized domains can track conversions using your publishable key.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>

    You can group your hostnames when adding them to the allow list:

    * `example.com`: Tracks traffic **only** from `example.com`.
    * `*.example.com`: Tracks traffic from **all subdomains** of `example.com`, but **not** from `example.com` itself.

    <Tip>
      When testing things out locally, you can add `localhost` to the **Allowed
      Hostnames** list temporarily. This will allow local events to be ingested by
      Dub. Don't forget to remove it once you're ready to go live!
    </Tip>
  </Step>

  <Step title="Generate your publishable key">
    Before you can track conversions on the client-side, you need to generate a [publishable key](/docs/api-reference/authentication#publishable-keys) from your Dub workspace.

    To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

    <Frame>
      <img alt="Enabling conversion tracking for a workspace" />
    </Frame>
  </Step>

  <Step title="Install Dub Analytics script">
    Next, install the Dub Analytics script on your website/web application.

    You can install the Dub Analytics script in several different ways:

    <CardGroup>
      <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

      <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

      <Card
        title="Framer"
        icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
        href="/docs/sdks/client-side/installation-guides/framer"
      />

      <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

      <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

      <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

      <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
    </CardGroup>

    <Note>
      You must configure the **publishable key** you generated in step 1 when
      installing the analytics script. Without this key, client-side conversion
      tracking will not work.
    </Note>

    <CodeGroup>
      ```html HTML theme={null}
      <script>
        !(function (c, n) {
          c[n] =
            c[n] ||
            function () {
              (c[n].q = c[n].q || []).push(arguments);
            };
          ["trackClick", "trackLead", "trackSale"].forEach(
            (t) => (c[n][t] = (...a) => c[n](t, ...a)),
          );
          var s = document.createElement("script");
          s.defer = 1;
          s.src = "https://www.dubcdn.com/analytics/script.conversion-tracking.js";
          s.setAttribute("data-publishable-key", "dub_pk_xxxxxxxx"); // Replace with your publishable key
          document.head.appendChild(s);
        })(window, "dubAnalytics");
      </script>
      ```

      ```typescript React/Next.js theme={null}
      import { Analytics as DubAnalytics } from '@dub/analytics/react';

      export default function RootLayout({
        children,
      }) {
        return (
          <html lang="en">
            <body className={inter.className}>{children}</body>
            <DubAnalytics
              ...
              publishableKey="dub_pk_xxxxxxxx" // Replace with your publishable key
            />
          </html>
        );
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Client-side lead tracking

Once the analytics script is installed, you can start tracking lead events in your application on the client-side.

### Track leads from URL query parameters (recommended)

If you redirect users to a thank-you page after a successful action, you can track leads by reading query parameters from the URL.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Thank You</title>
    </head>
    <body>
      <div>Thank you for signing up!</div>

      <script>
        // Get query parameters from URL
        const params = new URLSearchParams(window.location.search);
        const email = params.get("email");
        const name = params.get("name");

        if (email) {
          // Track the lead event
          dubAnalytics.trackLead({
            eventName: "Sign Up",
            customerExternalId: email, // can also be customer email
            customerName: name || undefined,
            customerEmail: email,
          });
        }
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useEffect } from "react";

  export function ThankYouPage() {
    const { trackLead } = useAnalytics();

    useEffect(() => {
      // Get query parameters from URL
      const params = new URLSearchParams(window.location.search);
      const email = params.get("email");
      const name = params.get("name");

      if (email) {
        // Track the lead event
        trackLead({
          eventName: "Sign Up",
          customerExternalId: email, // can also be customer email
          customerName: name || undefined,
          customerEmail: email,
        });
      }
    }, [trackLead]);

    return <div>Thank you for signing up!</div>;
  }
  ```
</CodeGroup>

### Track leads from form submissions

You can also track leads directly when users submit a form on your website.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Sign Up</title>
    </head>
    <body>
      <form id="signupForm">
        <input type="text" id="name" required />
        <input type="email" id="email" required />
        <button type="submit">Sign Up</button>
      </form>

      <script>
        document
          .getElementById("signupForm")
          .addEventListener("submit", function (e) {
            e.preventDefault();

            const name = document.getElementById("name").value;
            const email = document.getElementById("email").value;

            // Track the lead event
            dubAnalytics.trackLead({
              eventName: "Sign Up",
              customerExternalId: email,
              customerName: name,
              customerEmail: email,
            });
          });
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useState } from "react";

  export function SignUpForm() {
    const { trackLead } = useAnalytics();
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const handleSubmit = (e: React.FormEvent) => {
      e.preventDefault();

      // Track the lead event
      trackLead({
        eventName: "Sign Up",
        customerExternalId: email,
        customerName: name,
        customerEmail: email,
      });
    };

    return (
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <button type="submit">Sign Up</button>
      </form>
    );
  }
  ```
</CodeGroup>

Here are the properties you can include when sending a lead event:

| Property             | Required | Description                                                                                                                                                                                                                                                                                                                                                                            |
| :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickId`            | **Yes**  | The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided (i.e. if you're using [tracking a deferred lead event](/docs/conversions/leads/deferred)), Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found. |
| `eventName`          | **Yes**  | The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`).                                                                                                                                                                               |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                                                                                                                                                                                                                                               |
| `customerName`       | No       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                                                                                                                                                                                                                                                                                     |
| `customerEmail`      | No       | The email address of the customer.                                                                                                                                                                                                                                                                                                                                                     |
| `customerAvatar`     | No       | The avatar URL of the customer.                                                                                                                                                                                                                                                                                                                                                        |
| `mode`               | No       | The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request.                                                                                                                                                   |
| `metadata`           | No       | Additional metadata to be stored with the lead event. Max 10,000 characters.                                                                                                                                                                                                                                                                                                           |

**When to track leads**

You should track lead events after successful user actions such as:

* User registration or account creation
* Newsletter subscription
* Contact form submission
* Demo request or trial signup
* Download of gated content

Ensure the event is triggered **only after the backend confirms the action was completed successfully**. This guarantees accurate lead data and prevents false or incomplete entries.

## Client-side sale tracking

Once the analytics script is installed, you can start tracking sale events in your application on the client-side.

### Track sales from URL query parameters (recommended)

If you redirect users to a confirmation page after a successful purchase, you can track sales by reading query parameters from the URL.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Order Confirmation</title>
    </head>
    <body>
      <div>Thank you for your purchase!</div>

      <script>
        // Get query parameters from URL
        const params = new URLSearchParams(window.location.search);
        const customerId = params.get("customer_id");
        const amount = params.get("amount");
        const invoiceId = params.get("invoice_id");

        if (customerId && amount) {
          // Track the sale event
          dubAnalytics.trackSale({
            eventName: "Purchase",
            customerExternalId: customerId, // can also be customer email
            amount: parseInt(amount), // Amount in cents
            invoiceId: invoiceId || undefined,

            // Additional props for direct sale tracking (without prior lead event):
            // clickId: "cm3w...", // Read from dub_id cookie
            // customerName: "John Doe",
            // customerEmail: "john@example.com",
            // customerAvatar: "https://example.com/avatar.jpg",
            // currency: "usd",
            // paymentProcessor: "stripe",
            // metadata: { plan: "pro" },
          });
        }
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useEffect } from "react";

  export function OrderConfirmationPage() {
    const { trackSale } = useAnalytics();

    useEffect(() => {
      // Get query parameters from URL
      const params = new URLSearchParams(window.location.search);
      const customerId = params.get("customer_id");
      const amount = params.get("amount");
      const invoiceId = params.get("invoice_id");

      if (customerId && amount) {
        // Track the sale event
        trackSale({
          eventName: "Purchase",
          customerExternalId: customerId, // can also be customer email
          amount: parseInt(amount), // Amount in cents
          invoiceId: invoiceId || undefined,

          // Additional props for direct sale tracking (without prior lead event):
          // clickId: "cm3w...", // Read from dub_id cookie
          // customerName: "John Doe",
          // customerEmail: "john@example.com",
          // customerAvatar: "https://example.com/avatar.jpg",
        });
      }
    }, [trackSale]);

    return <div>Thank you for your purchase!</div>;
  }
  ```
</CodeGroup>

### Track sales from form submissions

You can also track sales directly when users complete a checkout form on your website.

<CodeGroup>
  ```html HTML theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Checkout</title>
    </head>
    <body>
      <form id="checkoutForm">
        ...
        <button type="submit">Checkout</button>
      </form>

      <script>
        document
          .getElementById("checkoutForm")
          .addEventListener("submit", function (e) {
            e.preventDefault();

            // Track the sale event
            dubAnalytics.trackSale({
              eventName: "Purchase",
              customerExternalId: "cus_RBfbD57H", // can also be customer email
              amount: 5000, // $50.00
              invoiceId: "in_1MtHbELkdIwH",

              // Additional props for direct sale tracking (without prior lead event):
              // clickId: "cm3w...", // Read from dub_id cookie
              // customerName: "John Doe",
              // customerEmail: "john@example.com",
              // customerAvatar: "https://example.com/avatar.jpg",
              // currency: "usd",
              // paymentProcessor: "stripe",
              // metadata: { plan: "pro" },
            });
          });
      </script>
    </body>
  </html>
  ```

  ```typescript React/Next.js theme={null}
  import { useAnalytics } from "@dub/analytics/react";
  import { useState } from "react";

  export function CheckoutForm() {
    const { trackSale } = useAnalytics();
    // …
  }
    const handleSubmit = (e: React.FormEvent) => {
      e.preventDefault();

      // Track the sale event
      trackSale({
        eventName: "Purchase",
        customerExternalId: "cus_RBfbD57H", // can also be customer email
        amount: 5000, // $50.00
        invoiceId: "in_1MtHbELkdIwH",

        // For direct sale tracking (without prior lead event):
        // clickId: "cm3w...", // Read from dub_id cookie
        // customerName: "John Doe",
        // customerEmail: "john@example.com",
        // customerAvatar: "https://example.com/avatar.jpg",
      });
    };

    return (
      <form onSubmit={handleSubmit}>
        ...
      </form>
    );
  }
  ```
</CodeGroup>

Here are the properties you can include when sending a sale event:

| Property             | Required | Description                                                                                                                                                |
| :------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerExternalId` | **Yes**  | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer.                                   |
| `amount`             | **Yes**  | The amount of the sale in cents.                                                                                                                           |
| `paymentProcessor`   | No       | The payment processor that processed the sale (e.g. [Stripe](/docs/integrations/stripe), [Shopify](/docs/integrations/stripe)). Defaults to "custom".      |
| `eventName`          | No       | The name of the event. Defaults to "Purchase".                                                                                                             |
| `invoiceId`          | No       | The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID.                                 |
| `currency`           | No       | The currency of the sale. Defaults to "usd".                                                                                                               |
| `metadata`           | No       | An object containing additional information about the sale.                                                                                                |
| `clickId`            | No       | **\[For direct sale tracking]**: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie. |
| `customerName`       | No       | **\[For direct sale tracking]**: The name of the customer. If not passed, a random name will be generated.                                                 |
| `customerEmail`      | No       | **\[For direct sale tracking]**: The email address of the customer.                                                                                        |
| `customerAvatar`     | No       | **\[For direct sale tracking]**: The avatar URL of the customer.                                                                                           |

**When to track sale**

Track sale events only after a user successfully completes a purchase or payment-related action, such as:

* Completing a checkout or order
* Subscription payment
* Invoice payment
* Any paid trial or demo conversion

Ensure the event is triggered **only after the backend confirms the payment was successful**. This guarantees accurate sale data and prevents false or incomplete entries.


# Cross-domain tracking
Source: https://dub.co/docs/sdks/client-side/features/cross-domain-tracking

Track conversions across domains

By default, the script already sets the `dub_id` cookie on a **cross-domain level**.

This means that if you have the script installed on your marketing site (e.g. **example.com**), the cookie will also be accessible when your user signs up for your app (e.g. **app.example.com**).

However, if you are installing the script on a subdomain (e.g. **app.example.com**), you will need to set the following option to make sure the cookie is accessible on the apex domain as well (e.g. **example.com**):

<CodeGroup>
  ```html HTML theme={null}
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    data-cookie-options='{"domain": ".example.com"}'
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  <DubAnalytics
    cookieOptions={{
      domain: ".example.com",
    }}
  />
  ```
</CodeGroup>

The script also supports conversion tracking across *entirely different domains*.

This means that if you have the script installed on a separate domain (e.g. **example.sh**), you can use the `outboundDomains` prop to ensure that the `dub_id` cookie value is automatically appended to all outbound links targeting your main domain (e.g. **example.com**).

<CodeGroup>
  ```html HTML theme={null}
  <script
    src="https://www.dubcdn.com/analytics/script.outbound-domains.js"
    data-domains="{'outbound': ['example.com', 'example.sh']}"
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // install this script on both domains
  <DubAnalytics
    domainsConfig={{
      outbound: ["example.com", "example.sh"],
    }}
  />
  ```
</CodeGroup>

<Tip>
  For outbound-domains support, you'll need to use the
  [`script.outbound-domains.js`](/docs/sdks/client-side/variants#outbound-domains-variant-script-outbound-domains-js)
  variant of the script. Learn more about [how script variants
  work](/docs/sdks/client-side/variants).
</Tip>


# Reverse-proxy support
Source: https://dub.co/docs/sdks/client-side/features/reverse-proxy-support

Track clicks on the client-side using a reverse proxy

## Tracking clicks via a reverse proxy

To avoid ad-blockers from blocking your click-tracking requests, we recommend setting up a reverse proxy.

Depending on which backend framework you're using, there are a few different ways to do this:

<CodeGroup>
  ```javascript Next.js theme={null}
  // next.config.js
  module.exports = {
    async rewrites() {
      return [
        {
          source: "/_proxy/dub/track/:path",
          destination: "https://api.dub.co/track/:path",
        },
      ];
    },
  };
  ```

  ```json Vercel theme={null}
  // vercel.json
  {
    "rewrites": [
      {
        "source": "/_proxy/dub/track/:path",
        "destination": "https://api.dub.co/track/:path"
      }
    ]
  }
  ```
</CodeGroup>

Once you've set up your reverse proxy, don't forget to update the `apiHost` parameter in the `<Analytics />` component to point to your proxy URL.

<CodeGroup>
  ```javascript HTML theme={null}
  // include this script tag in your HTML Head tag
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    data-api-host="/_proxy/dub"
    data-domains='{"refer": "go.example.com"}'
    defer
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          apiHost="/_proxy/dub" // the URL of your reverse proxy
          domainsConfig={{
            refer: "go.example.com", // the custom domain you're using on Dub for your short links
          }}
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```
</CodeGroup>

## Loading the script via a reverse proxy

To avoid ad-blockers from blocking the Dub Analytics script, it is recommended to use a reverse proxy.

Depending on which backend framework you're using, there are a few different ways to do this:

<CodeGroup>
  ```javascript Next.js theme={null}
  // next.config.js
  module.exports = {
    async rewrites() {
      return [
        {
          source: "/_proxy/dub/script.js",
          destination: "https://www.dubcdn.com/analytics/script.js",
        },
      ];
    },
  };
  ```

  ```json Vercel theme={null}
  // vercel.json
  {
    "rewrites": [
      {
        "source": "/_proxy/dub/script.js",
        "destination": "https://www.dubcdn.com/analytics/script.js"
      }
    ]
  }
  ```
</CodeGroup>

Once you've set up your reverse proxy, don't forget to update the `scriptProps.src` parameter in the `<Analytics />` component to point to your proxy URL.

<CodeGroup>
  ```javascript HTML theme={null}
  // include this script tag in your HTML Head tag
  <script
    src="/_proxy/dub/script.js" // pointing to your reverse proxy
    defer
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          scriptProps={{
            src: "/_proxy/dub/script.js", // pointing to your reverse proxy
          }}
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```
</CodeGroup>


# Framer
Source: https://dub.co/docs/sdks/client-side/installation-guides/framer

How to add the Dub Analytics script to your Framer site

With Dub Analytics, you can track lead and sale conversions on your Framer site, enabling you to measure the effectiveness of your marketing campaigns.

You can add the Dub Analytics script to your Framer website same way you would add Google Analytics script or any other JavaScript code.

Follow these steps to add the script to your site:

* Go to your Framer project and open the **Project Settings** menu.
* Open the **General** tab and scroll down to the **Custom Code** section.
* Paste the Dub analytics script in the **Start of head tag** section.
* Click on the **Save** button to save the changes.

```html theme={null}
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```

If you're using [Dub Partners](https://dub.co/partners) for affiliate management, you will also need to set up the `data-domains` property to enable [client-side click-tracking](/docs/sdks/client-side/features/click-tracking).

```html theme={null}
<script
  src="https://www.dubcdn.com/analytics/script.js"
  defer
  data-domains='{"refer":"yourcompany.link"}' // replace with your referral link domain on Dub
></script>
```

Read the [client-side click-tracking guide](/docs/sdks/client-side/features/click-tracking) for more information.

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Concepts

You can pass the following props to the Dub Analytics script to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="JSON-stringified object">
  Custom properties to pass to the cookie. Refer to
  [MDN's Set-Cookie documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for
  all available options.

  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute.

      For example, to set the cookie window to 60 days (instead of the default 90 days), you can add the following to your script:

      ```html theme={null}
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        defer
        data-cookie-options='{"expiresInDays": 60}'
      ></script>
      ```
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer) (previously `data-short-domain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing `dub_id` cookie
      will be automatically appended to all outbound links targeting these domains to enable
      cross-domain tracking across different applications.
      Example: `"dub.sh, git.new"`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>


# Google Tag Manager
Source: https://dub.co/docs/sdks/client-side/installation-guides/google-tag-manager

How to add the Dub Analytics script to your site using Google Tag Manager

This guide will walk you through the process of integrating Dub Analytics with Google Tag Manager (GTM).

<Steps>
  <Step title="Create a New Tag">
    First, navigate to your Google Tag Manager account and create a new tag:

    * Click on **Tags** in the left sidebar
    * Click the **New** button
    * Select **Custom HTML** as the tag type

    <Frame>
      <img alt="Select Custom HTML tag" />
    </Frame>
  </Step>

  <Step title="Add the Dub Analytics Script">
    In the Custom HTML section, you'll need to add the Dub Analytics script. Copy and paste the following code into the **HTML** field:

    ```js theme={null}
    <script>
      var script = document.createElement("script");
      script.defer = true;
      script.src = "https://www.dubcdn.com/analytics/script.js";
      document.getElementsByTagName("head")[0].appendChild(script);
    </script>
    ```

    <Frame>
      <img alt="Add dub analytics script" />
    </Frame>

    If you're using [Dub Partners](https://dub.co/partners) for affiliate management, you will also need to set up the `data-domains` property to enable [client-side click-tracking](/docs/sdks/client-side/features/click-tracking).

    ```js theme={null}
    <script>
      ...
      var script = document.createElement("script");
      script.defer = true;
      script.src = "https://www.dubcdn.com/analytics/script.js";
      script.dataset.domains = JSON.stringify({ refer: "refer.yourdomain.com" }); // Add this line to match the short domain you're using for your referral links
      document.getElementsByTagName("head")[0].appendChild(script);
    </script>
    ```
  </Step>

  <Step title="Configure the Trigger">
    To ensure the analytics script loads on all pages:

    * Click on the **Triggering** section
    * Select **All Pages** as the trigger type
    * This will make the tag fire on every page load
  </Step>

  <Step title="Save and Publish">
    * Name your tag **Dub Analytics**
    * Click **Save** to store your changes
    * Click **Submit** to create a new version
    * Finally, click **Publish** to activate the tag on your website
  </Step>
</Steps>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Concepts

You can pass the following props to the Dub Analytics script to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="JSON-stringified object">
  Custom properties to pass to the cookie. Refer to
  [MDN's Set-Cookie documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for
  all available options.

  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute.

      For example, to set the cookie window to 60 days (instead of the default 90 days), you can add the following to your script:

      ```html theme={null}
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        defer
        data-cookie-options='{"expiresInDays": 60}'
      ></script>
      ```
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer) (previously `data-short-domain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing `dub_id` cookie
      will be automatically appended to all outbound links targeting these domains to enable
      cross-domain tracking across different applications.
      Example: `"dub.sh, git.new"`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>


# Manual Installation
Source: https://dub.co/docs/sdks/client-side/installation-guides/manual

How to add the Dub Analytics script to your website

With Dub Analytics, you can track lead and sale conversions on your website, enabling you to measure the effectiveness of your marketing campaigns.

You can add the Dub Analytics script to your website same way you would add Google Analytics script or any other JavaScript code – by adding the Dub Analytics script in the `<head>` section of your HTML file.

```html theme={null}
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```

If you're using [Dub Partners](https://dub.co/partners) for affiliate management, you will also need to set up the `data-domains` property to enable [client-side click-tracking](/docs/sdks/client-side/features/click-tracking).

```html theme={null}
<script
  src="https://www.dubcdn.com/analytics/script.js"
  defer
  data-domains='{"refer":"yourcompany.link"}' // replace with your referral link domain on Dub
></script>
```

Read the [client-side click-tracking guide](/docs/sdks/client-side/features/click-tracking) for more information.

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Concepts

You can pass the following props to the Dub Analytics script to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="JSON-stringified object">
  Custom properties to pass to the cookie. Refer to
  [MDN's Set-Cookie documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for
  all available options.

  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute.

      For example, to set the cookie window to 60 days (instead of the default 90 days), you can add the following to your script:

      ```html theme={null}
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        defer
        data-cookie-options='{"expiresInDays": 60}'
      ></script>
      ```
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer) (previously `data-short-domain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing `dub_id` cookie
      will be automatically appended to all outbound links targeting these domains to enable
      cross-domain tracking across different applications.
      Example: `"dub.sh, git.new"`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>


# React
Source: https://dub.co/docs/sdks/client-side/installation-guides/react

How to add the Dub Analytics script to your React website

With Dub Analytics, you can track lead and sale conversions on your website, enabling you to measure the effectiveness of your marketing campaigns.

## Quickstart

This quick start guide will show you how to get started with Dub Analytics on your website.

<Steps>
  <Step title="Install package">
    Using the package manager of your choice, add the `@dub/analytics` to your project.

    <CodeGroup>
      ```bash npm theme={null}
      npm install @dub/analytics
      ```

      ```bash pnpm theme={null}
      pnpm add @dub/analytics
      ```

      ```bash yarn theme={null}
      yarn add @dub/analytics
      ```

      ```bash bun theme={null}
      bun add @dub/analytics
      ```
    </CodeGroup>
  </Step>

  <Step title="Initialize package in your code">
    If you are using a React framework, you can use the `<Analytics />` component to track conversions on your website.

    E.g. if you're using Next.js, you can add the `<Analytics />` component to your root layout component or any other pages where you want to track conversions.

    ```jsx app/layout.tsx theme={null}
    import { Analytics as DubAnalytics } from '@dub/analytics/react';

    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <body>{children}</body>
          <DubAnalytics />
        </html>
      );
    }
    ```
  </Step>

  <Step title="Optional: Set up client-side click tracking">
    If you're using [Dub Partners](https://dub.co/partners) for affiliate management,  you will also need to set up the `domainsConfig.refer` property to enable [client-side click-tracking](/docs/sdks/client-side/features/click-tracking).

    ```jsx app/layout.tsx theme={null}
    import { Analytics as DubAnalytics } from '@dub/analytics/react';

    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <body>{children}</body>
          <DubAnalytics domainsConfig={{
            refer: "yourcompany.link"
          }} />
        </html>
      );
    }
    ```

    Read the [client-side click-tracking guide](/docs/sdks/client-side/features/click-tracking) for more information.
  </Step>
</Steps>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Concepts

You can pass the following props to the `<Analytics />` component to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="CookieOption Object">
  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute. Example: `90`
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer)
      (previously `shortDomain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing
      `dub_id` cookie will be automatically appended to all outbound links
      targeting these domains to enable cross-domain tracking across different
      applications.
      Example: `["dub.sh", "git.new"]`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>

<ParamField type="HTMLScriptElement Object">
  Custom properties to pass to the script tag. Refer to
  [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement) for
  all available options.
</ParamField>


# Shopify
Source: https://dub.co/docs/sdks/client-side/installation-guides/shopify

How to add the Dub Analytics script to your Shopify store

With Dub Analytics, you can track lead and sale conversions on your Shopify store, enabling you to measure the effectiveness of your marketing campaigns.

You can add the Dub Analytics script to your Shopify store simply by installing the [Dub Shopify App](https://d.to/shopify/app) from the App Store.

<Frame>
  <img alt="The connection status in the Dub app" />
</Frame>

Then, make sure to activate the Dub Analytics script by following these steps:

1. Navigate to your Shopify admin panel.
2. Go to **Online Store** > **Themes**.
3. Click on **Customize** for your current theme.
4. In the theme editor, select the **App embeds** tab.
5. Locate the **Analytics Script** for the Dub app and toggle it to activate.

<Frame>
  <img alt="Enable the Dub Analytics script in your Shopify theme" />
</Frame>

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Installation video

Here's a video showing how to install and activate the Dub Analytics script in your Shopify store:

<iframe title="Loom video player" />


# Webflow
Source: https://dub.co/docs/sdks/client-side/installation-guides/webflow

How to add the Dub Analytics script to your Webflow site

With Dub Analytics, you can track lead and sale conversions on your Webflow site, enabling you to measure the effectiveness of your marketing campaigns.

You can add the Dub Analytics script to your Webflow website same way you would add Google Analytics script or any other JavaScript code.

Follow these steps to add the script to your site:

* On your project's page, click on the **Webflow logo** in the left-hand side menu and choose **Project Settings**.
* Choose **[Custom Code](https://university.webflow.com/lesson/custom-code-in-the-head-and-body-tags?topics=site-settings)** from the menu and paste the Dub analytics script in the **Head Code** section.
* Click on the **Save Changes** button and then **Publish** your changes.

```html theme={null}
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```

If you're using [Dub Partners](https://dub.co/partners) for affiliate management, you will also need to set up the `data-domains` property to enable [client-side click-tracking](/docs/sdks/client-side/features/click-tracking).

```html theme={null}
<script
  src="https://www.dubcdn.com/analytics/script.js"
  defer
  data-domains='{"refer":"yourcompany.link"}' // replace with your referral link domain on Dub
></script>
```

Read the [client-side click-tracking guide](/docs/sdks/client-side/features/click-tracking) for more information.

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Concepts

You can pass the following props to the Dub Analytics script to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="JSON-stringified object">
  Custom properties to pass to the cookie. Refer to
  [MDN's Set-Cookie documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for
  all available options.

  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute.

      For example, to set the cookie window to 60 days (instead of the default 90 days), you can add the following to your script:

      ```html theme={null}
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        defer
        data-cookie-options='{"expiresInDays": 60}'
      ></script>
      ```
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer) (previously `data-short-domain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing `dub_id` cookie
      will be automatically appended to all outbound links targeting these domains to enable
      cross-domain tracking across different applications.
      Example: `"dub.sh, git.new"`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>


# WordPress
Source: https://dub.co/docs/sdks/client-side/installation-guides/wordpress

How to add the Dub Analytics script to your WordPress site

With Dub Analytics, you can track lead and sale conversions on your WordPress site, enabling you to measure the effectiveness of your marketing campaigns.

You can add the Dub Analytics script to your WordPress website same way you would add Google Analytics script or any other JavaScript code.

Follow these steps to add the script to your site:

* On your WordPress dashboard, navigate to the **Theme Editor** section under the **Appearance** menu.
* Open the **Theme Header (header.php)** file on the right column.
* Paste the Dub analytics script in the header area.
* Click on the **Update File** button to save the changes.

```html theme={null}
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```

If you're using [Dub Partners](https://dub.co/partners) for affiliate management, you will also need to set up the `data-domains` property to enable [client-side click-tracking](/docs/sdks/client-side/features/click-tracking).

```html theme={null}
<script
  src="https://www.dubcdn.com/analytics/script.js"
  defer
  data-domains='{"refer":"yourcompany.link"}' // replace with your referral link domain on Dub
></script>
```

Read the [client-side click-tracking guide](/docs/sdks/client-side/features/click-tracking) for more information.

<Check>
  You can **verify the installation** with the following tests:

  1. Open the browser console and type in `_dubAnalytics` – if the script is installed correctly, you should see the `_dubAnalytics` object in the console.
  2. Add the `?dub_id=test` query parameter to your website URL and make sure that the `dub_id` cookie is being set in your browser.

  If both of these checks pass, the script is installed correctly. Otherwise, please make sure:

  * The analytics script was added to the `<head>` section of the page
  * If you're using a content delivery network (CDN), make sure to purge any cached content
</Check>

## Concepts

You can pass the following props to the Dub Analytics script to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="JSON-stringified object">
  Custom properties to pass to the cookie. Refer to
  [MDN's Set-Cookie documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for
  all available options.

  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute.

      For example, to set the cookie window to 60 days (instead of the default 90 days), you can add the following to your script:

      ```html theme={null}
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        defer
        data-cookie-options='{"expiresInDays": 60}'
      ></script>
      ```
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer) (previously `data-short-domain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing `dub_id` cookie
      will be automatically appended to all outbound links targeting these domains to enable
      cross-domain tracking across different applications.
      Example: `"dub.sh, git.new"`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>


# Introduction
Source: https://dub.co/docs/sdks/client-side/introduction

Learn more about the Dub Analytics script and how to install it.

Dub Analytics (`@dub/analytics`) is a lightweight (\~1kb), [open-source](https://github.com/dubinc/analytics) client-side script for [tracking conversion events](/help/article/dub-conversions) with Dub.

The script handles the detection of the `dub_id` query parameter and storing it as a first-party cookie, which will be used to attribute subsequent conversion events to the original link.

<Frame>
  <img alt="A diagram showing how click events are tracked in the conversion funnel" />
</Frame>

If you're using Dub Partners, this script also lets you [track clicks on the client-side](/docs/sdks/client-side/features/click-tracking) using query parameters (e.g. `?via=john`). This gives you the flexibility to track clicks directly on your website or app, without needing to rely on link redirects.

## Installation guides

You can install the Dub Analytics script in several different ways:

<CardGroup>
  <Card title="React" icon="react" href="/docs/sdks/client-side/installation-guides/react" />

  <Card title="Manual installation" icon="browser" href="/docs/sdks/client-side/installation-guides/manual" />

  <Card
    title="Framer"
    icon={
  <svg
    width="74"
    height="111"
    viewBox="0 0 74 111"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className="w-7 h-7"
  >
    <path d="M0 0H73.8374V36.9892H36.9187L0 0Z" fill="#155dfc" />
    <path d="M0 36.989H36.9187L73.8374 73.9796H0V36.989Z" fill="#155dfc" />
    <path d="M0 73.9797H36.9187V110.97L0 73.9797Z" fill="#155dfc" />
  </svg>
}
    href="/docs/sdks/client-side/installation-guides/framer"
  />

  <Card title="Shopify" icon="shopify" href="/docs/sdks/client-side/installation-guides/shopify" />

  <Card title="WordPress" icon="wordpress" href="/docs/sdks/client-side/installation-guides/wordpress" />

  <Card title="Webflow" icon="webflow" href="/docs/sdks/client-side/installation-guides/webflow" />

  <Card title="Google Tag Manager" icon="google" href="/docs/sdks/client-side/installation-guides/google-tag-manager" />
</CardGroup>

## Features

The Dub Analytics script comes with the following features:

* [Conversion-tracking](/docs/sdks/client-side/features/conversion-tracking)
* [Client-side click-tracking](/docs/sdks/client-side/features/click-tracking)
* [Cross-domain tracking](/docs/sdks/client-side/features/cross-domain-tracking)
* [Reverse-proxy support](/docs/sdks/client-side/features/reverse-proxy-support)

## Properties

You can pass the following props to the Dub Analytics script to customize its behavior:

<ParamField type="url">
  The base URL for the Dub API. This is useful for [setting up reverse
  proxies](/docs/sdks/client-side/features/reverse-proxy-support) to avoid
  adblockers.
</ParamField>

<ParamField type="first-click | last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  * `first-click`: The first click model
    gives all the credit to the first touchpoint in the customer journey.
  * `last-click`: The last click model gives all the credit to the last
    touchpoint in the customer journey.
</ParamField>

<ParamField type="JSON-stringified object">
  Custom properties to pass to the cookie. Refer to
  [MDN's Set-Cookie documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for
  all available options.

  <Expandable title="properties">
    <ParamField type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField type="integer">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField type="integer">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute.

      For example, to set the cookie window to 60 days (instead of the default 90 days), you can add the following to your script:

      ```html theme={null}
      <script
        src="https://www.dubcdn.com/analytics/script.js"
        defer
        data-cookie-options='{"expiresInDays": 60}'
      ></script>
      ```
    </ParamField>

    <ParamField type="string">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="JSON-stringified object">
  Configure the domains that Dub will track. The following properties are available:

  <Expandable title="properties">
    <ParamField type="string">
      The Dub custom domain for [referral program client-side click tracking](http://d.to/clicks/refer) (previously `data-short-domain`).
      Example: `refer.dub.co`
    </ParamField>

    <ParamField type="string">
      The Dub short domain for tracking site visits.
      Example: `site.dub.co`
    </ParamField>

    <ParamField type="string | string[]">
      An array of domains for cross-domain tracking. When configured, the existing `dub_id` cookie
      will be automatically appended to all outbound links targeting these domains to enable
      cross-domain tracking across different applications.
      Example: `"dub.sh, git.new"`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField type="string[]">
  An array of query parameters to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>

## Open-source examples

Here are some open-source code examples that you can reference:

<CardGroup>
  <Card title="Dub Analytics with Client-side Click Tracking + Reverse Proxy" icon="github" href="https://github.com/dubinc/analytics/tree/main/apps/nextjs-reverse-proxy">
    See the full example on GitHub.
  </Card>

  <Card title="Dub Analytics with Geolocation" icon="github" href="https://github.com/dubinc/analytics/tree/main/apps/nextjs-geolocation-script">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Script Variants
Source: https://dub.co/docs/sdks/client-side/variants

Learn more about the different variants of the Dub Analytics script.

Inspired by [Plausible](https://plausible.io/), our script is split into multiple variants to help you optimize your script for different use cases.

This allows us to keep the base variant of the script as lightweight as possible (\~1kb) while still allowing you to use the script in more complex use cases.

## Syntax

The base script is available on [`script.js`](https://www.dubcdn.com/analytics/script.js), and all variants are available on `script.[variant].js`.

For instance, the `outbound-domains` variant is available on [`script.outbound-domains.js`](https://www.dubcdn.com/analytics/script.outbound-domains.js).

You can also mix and match variants. For example, you can use the `site-visit` and `outbound-domains` variants together with this script: [`script.site-visit.outbound-domains.js`](https://www.dubcdn.com/analytics/script.site-visit.outbound-domains.js).

## List of variants

Here's a list of all the variants available:

* [Base Variant](#base-variant-script-js)
* [Site Visit Variant](#site-visit-variant-script-site-visit-js)
* [Outbound Domains Variant](#outbound-domains-variant-script-outbound-domains-js)
* [Combined Variant](#combined-variant)

### Base Variant (`script.js`)

The base variant of the script is the most lightweight variant of the script. It supports the following features:

* Detecting the `dub_id` query parameter and storing it as a first-party cookie.
* Tracking [client-side click events for referral programs](/docs/sdks/client-side/features/click-tracking).
* Setting [custom cookie window](/docs/sdks/client-side/introduction#custom-cookie-window) and [attribution models](/docs/sdks/client-side/introduction#param-data-attribution-model)

Here's how you can use the base variant:

<CodeGroup>
  ```html HTML theme={null}
  <script src="https://www.dubcdn.com/analytics/script.js"></script>
  ```

  ```typescript React/Next.js theme={null}
  <DubAnalytics />
  ```
</CodeGroup>

### Site Visit Variant (`script.site-visit.js`)

<Note>Dub Analytics site visit feature is still in beta.</Note>

The site visit variant of the script is a variant of the script that supports tracking site visits.

On top of the features supported by the base variant, it also supports tracking the first entry page of a user, which is useful for measuring SEO and Google Ads performance.

Here's how you can use the site visit variant:

<CodeGroup>
  ```html HTML theme={null}
  <script
    src="https://www.dubcdn.com/analytics/script.site-visit.js"
    data-domains="{'site': 'site.dub.co'}"
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // the DubAnalytics component automatically detects the `domainsConfig.site` prop
  // and applies the site-visit script variant for you
  <DubAnalytics domainsConfig={{ site: "site.dub.co" }} />
  ```
</CodeGroup>

### Outbound Domains Variant (`script.outbound-domains.js`)

The outbound domains variant of the script is a variant of the script that supports [cross-domain tracking](/docs/sdks/client-side/introduction#cross-domain-tracking) across different applications.

On top of the features supported by the base variant, it also supports appending the `dub_id` cookie to all outbound links targeting the domains you configure.

Here's how you can use the outbound domains variant:

<CodeGroup>
  ```html HTML theme={null}
  <script
    src="https://www.dubcdn.com/analytics/script.outbound-domains.js"
    data-domains="{'outbound': ['example.com', 'example.sh']}"
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // the DubAnalytics component automatically detects the `domainsConfig.outbound` prop
  // and applies the outbound-domains script variant for you
  <DubAnalytics domainsConfig={{ outbound: ["example.com", "example.sh"] }} />
  ```
</CodeGroup>

### Combined Variant

You can also mix and match variants. For example, you can use the `site-visit` and `outbound-domains` variants together with this script: [`script.site-visit.outbound-domains.js`](https://www.dubcdn.com/analytics/script.site-visit.outbound-domains.js).

Here's how you can use the combined variant:

<CodeGroup>
  ```html HTML theme={null}
  <script
    src="https://www.dubcdn.com/analytics/script.site-visit.outbound-domains.js"
    data-domains="{'site': 'site.dub.co', 'outbound': ['example.com', 'example.sh']}"
  ></script>
  ```

  ```typescript React/Next.js theme={null}
  // the DubAnalytics component automatically detects the `domainsConfig` prop
  // and applies the combined script variant for you
  <DubAnalytics
    domainsConfig={{
      site: "site.dub.co",
      outbound: ["example.com", "example.sh"],
    }}
  />
  ```
</CodeGroup>

## `DubAnalytics` React Component

If you're using a React application, we recommend using the `DubAnalytics` component to automatically apply the correct script variant for you.

For example, if you want to use the `outbound-domains` variant, you can do the following:

```typescript React/Next.js theme={null}
<DubAnalytics domainsConfig={{ outbound: ["example.com", "example.sh"] }} />
```

The `DubAnalytics` component will automatically detect the `domainsConfig` prop and apply the correct script variant for you.


# Go SDK
Source: https://dub.co/docs/sdks/go

Learn how to integrate Dub with Go.

## Installation

```bash theme={null}
go get github.com/dubinc/dub-go
```

## Basic Usage

Here's how you can use the Dub Go SDK to create a link and retrieve click analytics in timeseries format for it:

```go theme={null}
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	dub "github.com/dubinc/dub-go"
)

func main() {
	// Initialize the Dub SDK with your API key
	d := dub.New(
		dub.WithSecurity(os.Getenv("DUB_API_KEY")), // optional, defaults to DUB_API_KEY
	)

	// Create a new link
	request := &operations.CreateLinkRequestBody{
		URL: "https://google.com",
	}

	ctx := context.Background()
	res, err := d.Links.Create(ctx, request)
	if err != nil {
		log.Fatal(err)
	}
	if res.LinkSchema != nil {
		fmt.Println(res.LinkSchema.ShortLink) // e.g. https://dub.sh/abc123
	}

	// Get analytics for the link
	analyticsRequest := operations.RetrieveAnalyticsRequest{
		LinkId:   res.LinkSchema.ID,
		GroupBy:  "timeseries",
		Interval: "30d",
	}

	analyticsRes, err := d.Analytics.Retrieve(ctx, analyticsRequest)
	if err != nil {
		log.Fatal(err)
	}
	if analyticsRes.OneOf != nil {
		fmt.Println(analyticsRes.OneOf) // e.g. [{ start: "2024-01-01", clicks: 100 }]
	}
}
```

For more usage examples:

1. [Organizing links by external ID, tenant ID, tags, etc](/docs/concepts/links/organization)
2. [Bulk link operations (create, update, delete)](/docs/concepts/links/bulk-operations)
3. [Retrieving link analytics](/docs/concepts/analytics)

You can also check out the [Go SDK quickstart](/docs/sdks/quickstart/go) for a basic example.

## Additional Resources

<CardGroup>
  <Card title="Go Package" icon="golang" href="https://d.to/go/sdk">
    Download and install the Dub Go SDK on GitHub
  </Card>

  <Card title="SDK Reference" icon="book" href="https://github.com/dubinc/dub-go/blob/main/README.md">
    View the complete SDK reference documentation
  </Card>

  <Card title="Examples" icon="github" href="https://github.com/dubinc/examples/tree/main/go">
    Quickstart examples with the Go SDK
  </Card>
</CardGroup>


# Overview
Source: https://dub.co/docs/sdks/overview

Open-source client libraries for the Dub API

## MCP Server

<Card title="Dub MCP Server" icon="robot" href="/docs/mcp-server">
  Use the Dub MCP server to manage your partner program with the AI agent of
  your choice.
</Card>

## Server-side SDKs

Dub offers server-side SDKs for many popular programming languages:

<CardGroup>
  <Card title="TypeScript" icon="npm" href="/docs/sdks/typescript">
    TypeScript library for the Dub API
  </Card>

  <Card title="Go" icon="golang" href="/docs/sdks/go">
    Go library for the Dub API
  </Card>

  <Card title="Python" icon="python" href="/docs/sdks/python">
    Python library for the Dub API
  </Card>

  <Card title="Ruby" icon="gem" href="/docs/sdks/ruby">
    Ruby library for the Dub API
  </Card>

  <Card title="PHP" icon="php" href="/docs/sdks/php">
    PHP library for the Dub API
  </Card>
</CardGroup>

## Client-side SDKs

<CardGroup>
  <Card title="@dub/analytics" icon="js" href="/docs/sdks/client-side/introduction">
    Dub Analytics SDK
  </Card>
</CardGroup>

## Client-side mobile SDKs

<CardGroup>
  <Card title="dub-ios" icon="swift" href="/docs/sdks/client-side-mobile/installation-guides/swift">
    Dub iOS SDK
  </Card>

  <Card title="dub-react-native" icon="react" href="/docs/sdks/client-side-mobile/installation-guides/react-native">
    Dub React Native SDK
  </Card>
</CardGroup>

## Embedded Dashboards

<CardGroup>
  <Card title="Referrals Embed" icon="window" href="/docs/sdks/embed/referrals">
    Embed the Dub Referrals dashboard in your application
  </Card>
</CardGroup>

## CLI

<CardGroup>
  <Card title="Dub CLI" icon="terminal" href="/docs/sdks/cli">
    Shorten and manage your links directly from your terminal
  </Card>
</CardGroup>


# PHP SDK
Source: https://dub.co/docs/sdks/php

Learn how to integrate Dub with PHP.

## Installation

```bash theme={null}
composer require dub/dub-php
```

## Basic Usage

Here's how you can use the Dub PHP SDK to create a link and retrieve click analytics in timeseries format for it:

```php theme={null}
<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use Dub\Dub;
use Dub\Models\Operations;

// Initialize the Dub SDK with your API key
$dub = Dub::builder()
    ->setSecurity(getenv('DUB_API_KEY')) // optional, defaults to DUB_API_KEY
    ->build();

// Create a new link
$request = new Operations\CreateLinkRequestBody(
    url: 'https://google.com',
);

try {
    $response = $dub->links->create($request);

    if ($response->linkSchema !== null) {
        echo $response->linkSchema->shortLink; // e.g. https://dub.sh/abc123
    }

    // Get analytics for the link
    $analyticsRequest = new Operations\RetrieveAnalyticsRequest();
    $analyticsRequest->linkId = $response->linkSchema->id;
    $analyticsRequest->interval = Operations\Interval::ThirtyD;
    $analyticsRequest->groupBy = Operations\GroupBy::Timeseries;

    $analyticsResponse = $dub->analytics->retrieve($analyticsRequest);

    if ($analyticsResponse->oneOf !== null) {
        print_r($analyticsResponse->oneOf); // e.g. [{ start: "2024-01-01", clicks: 100 }]
    }
} catch (Throwable $e) {
    // handle exception
}
```

For more usage examples:

1. [Organizing links by external ID, tenant ID, tags, etc](/docs/concepts/links/organization)
2. [Bulk link operations (create, update, delete)](/docs/concepts/links/bulk-operations)
3. [Retrieving link analytics](/docs/concepts/analytics)

## Frameworks

You can use the Dub PHP SDK with any PHP framework:

1. [Usage with Laravel](/docs/sdks/quickstart/laravel)

If you're using a different PHP framework, you can refer to the [PHP SDK quickstart](/docs/sdks/quickstart/php) for a basic example.

## Additional Resources

<CardGroup>
  <Card title="Packagist" icon="php" href="https://d.to/php/sdk">
    Download and install the Dub PHP SDK on Packagist
  </Card>

  <Card title="SDK Reference" icon="book" href="https://github.com/dubinc/dub-php/blob/main/README.md">
    View the complete SDK reference documentation
  </Card>

  <Card title="Examples" icon="github" href="https://github.com/dubinc/examples/tree/main/php">
    Quickstart examples with the PHP SDK
  </Card>

  <Card title="Source Code" icon="github" href="https://github.com/dubinc/dub-php">
    View the complete source code for the Dub PHP SDK
  </Card>
</CardGroup>


# Python SDK
Source: https://dub.co/docs/sdks/python

Learn how to integrate Dub with Python.

## Installation

```bash theme={null}
pip install dub
```

## Basic Usage

Here's how you can use the Dub Python SDK to create a link and retrieve click analytics in timeseries format for it:

```python theme={null}
import os
import dub
from dub.models import operations

# Initialize the Dub SDK with your API key
d = dub.Dub(
    token=os.environ['DUB_API_KEY'], # optional, defaults to DUB_API_KEY
)

# Create a new link
res = d.links.create(request={
    "url": "https://google.com",
})

print(res.short_link) # e.g. https://dub.sh/abc123

# Get analytics for the link
analytics = d.analytics.retrieve(request={
    "link_id": res.id,
    "interval": "30d",
    "group_by": "timeseries",
})

print(analytics) # e.g. [{ "start": "2024-01-01", "clicks": 100 }]
```

For more usage examples:

1. [Organizing links by external ID, tenant ID, tags, etc](/docs/concepts/links/organization)
2. [Bulk link operations (create, update, delete)](/docs/concepts/links/bulk-operations)
3. [Retrieving link analytics](/docs/concepts/analytics)

## Frameworks

You can use the Dub Python SDK with any Python framework:

1. [Usage with Flask](/docs/sdks/quickstart/flask)
2. [Usage with Django](/docs/sdks/quickstart/django)

If you're using a different Python framework, you can refer to the [Python SDK quickstart](/docs/sdks/quickstart/python) for a basic example.

## Additional Resources

<CardGroup>
  <Card title="PyPI Package" icon="python" href="https://d.to/python/sdk">
    Download and install the Dub Python SDK on PyPI
  </Card>

  <Card title="SDK Reference" icon="book" href="https://github.com/dubinc/dub-python/blob/main/README.md">
    View the complete SDK reference documentation
  </Card>

  <Card title="Examples" icon="github" href="https://github.com/dubinc/examples/tree/main/python">
    Quickstart examples with the Python SDK
  </Card>

  <Card title="Source Code" icon="github" href="https://github.com/dubinc/dub-python">
    View the complete source code for the Dub Python SDK
  </Card>
</CardGroup>


# Ruby SDK
Source: https://dub.co/docs/sdks/ruby

Learn how to integrate Dub with Ruby.

## Installation

```bash theme={null}
gem install dub
```

## Basic Usage

Here's how you can use the Dub Ruby SDK to create a link and retrieve click analytics in timeseries format for it:

```ruby theme={null}
require 'dub'

# Initialize the Dub SDK with your API key
dub = ::OpenApiSDK::Dub.new
dub.config_security(
  ::OpenApiSDK::Shared::Security.new(
    token: ENV['DUB_API_KEY'], # optional, defaults to DUB_API_KEY
  )
)

# Create a new link
req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
  request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://google.com"
  )
)

res = dub.links.create(req)
puts res.raw_response.body # e.g. { "shortLink": "https://dub.sh/abc123" }

# Get analytics for the link
analytics_req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
  link_id: res.raw_response.body["id"],
  interval: ::OpenApiSDK::Operations::Interval::THIRTYD,
  group_by: ::OpenApiSDK::Operations::GroupBy::TIMESERIES
)

analytics_res = dub.analytics.retrieve(analytics_req)
puts analytics_res.raw_response.body # e.g. [{ "start": "2024-01-01", "clicks": 100 }]
```

For more usage examples:

1. [Organizing links by external ID, tenant ID, tags, etc](/docs/concepts/links/organization)
2. [Bulk link operations (create, update, delete)](/docs/concepts/links/bulk-operations)
3. [Retrieving link analytics](/docs/concepts/analytics)

## Frameworks

You can use the Dub Ruby SDK with any Ruby framework:

1. [Usage with Rails](/docs/sdks/quickstart/rails)
2. [Usage with Sinatra](/docs/sdks/quickstart/sinatra)

If you're using a different Ruby framework, you can refer to the [Ruby SDK quickstart](/docs/sdks/quickstart/ruby) for a basic example.

## Additional Resources

<CardGroup>
  <Card title="RubyGems Package" icon="gem" href="https://d.to/ruby/sdk">
    Download and install the Dub Ruby SDK on RubyGems
  </Card>

  <Card title="SDK Reference" icon="book" href="https://github.com/dubinc/dub-ruby/blob/main/README.md">
    View the complete SDK reference documentation
  </Card>

  <Card title="Examples" icon="github" href="https://github.com/dubinc/examples/tree/main/ruby">
    Quickstart examples with the Ruby SDK
  </Card>

  <Card title="Source Code" icon="github" href="https://github.com/dubinc/dub-ruby">
    View the complete source code for the Dub Ruby SDK
  </Card>
</CardGroup>


# TypeScript SDK
Source: https://dub.co/docs/sdks/typescript

Learn how to integrate Dub with TypeScript.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install dub
  ```

  ```bash pnpm theme={null}
  pnpm add dub
  ```

  ```bash yarn theme={null}
  yarn add dub zod # zod is a peer dependency
  ```
</CodeGroup>

## Basic Usage

Here's how you can use the Dub TypeScript SDK to create a link and retrieve click analytics in timeseries format for it:

```typescript theme={null}
import { Dub } from "dub";

// Initialize the Dub SDK with your API key
const dub = new Dub({
  token: process.env.DUB_API_KEY, // optional, defaults to DUB_API_KEY
});

// Create a new link
const link = await dub.links.create({
  url: "https://google.com",
});

console.log(link.shortLink); // e.g. https://dub.sh/abc123

// Get analytics for the link
const analytics = await dub.analytics.retrieve({
  link_id: link.id,
  groupBy: "timeseries",
  interval: "30d",
});

console.log(analytics); // e.g. [{ start: "2024-01-01", clicks: 100 }]
```

For more usage examples:

1. [Organizing links by external ID, tenant ID, tags, etc](/docs/concepts/links/organization)
2. [Bulk link operations (create, update, delete)](/docs/concepts/links/bulk-operations)
3. [Retrieving link analytics](/docs/concepts/analytics)

## Frameworks

You can use the Dub TypeScript SDK with any JavaScript framework:

1. [Usage with Next.js](/docs/sdks/quickstart/nextjs)
2. [Usage with Remix](/docs/sdks/quickstart/remix)
3. [Usage with Nuxt](/docs/sdks/quickstart/nuxt)
4. [Usage with Express](/docs/sdks/quickstart/express)

If you're using a different JavaScript framework, you can refer to the [TypeScript SDK quickstart](/docs/sdks/quickstart/typescript) for a basic example.

## Additional Resources

<CardGroup>
  <Card title="NPM Package" icon="npm" href="https://d.to/ts/sdk">
    Download and install the Dub TypeScript SDK on NPM
  </Card>

  <Card title="SDK Reference" icon="book" href="https://github.com/dubinc/dub-ts/blob/main/README.md">
    View the complete SDK reference documentation
  </Card>

  <Card title="Examples" icon="github" href="https://github.com/dubinc/examples/tree/main/typescript">
    Quickstart examples with the TypeScript SDK
  </Card>

  <Card title="Source Code" icon="github" href="https://github.com/dubinc/dub-ts">
    View the complete source code for the Dub TypeScript SDK
  </Card>
</CardGroup>


# Self-hosting Dub
Source: https://dub.co/docs/self-hosting

An end-to-end guide on how to self-host Dub – the open-source link attribution platform.

<Frame>
  <img alt="Dub Logo on a gradient background" />
</Frame>

You can self-host Dub on your own servers and cloud infrastructure for greater control over your data and design. This guide will walk you through the entire process of setting up Dub on your own servers.

## Prerequisites

Before you begin, make sure you have the following:

* A [GitHub](https://github.com/) account
* A [Tinybird](https://www.tinybird.co/) account
* An [Upstash](https://upstash.com/) account
* A [PlanetScale](https://planetscale.com/) account
* A [Vercel](https://vercel.com/) account
* Either a [Cloudflare](https://www.cloudflare.com/) or [AWS](https://aws.com) account

You'll also need a custom domain that you will be using for your Dub instance, with an optional custom short domain for your links.

In this guide, we'll use `acme.com` as a placeholder for your custom domain, and `ac.me` as a placeholder for your custom short domain.

## Step 1: Local setup

First, you'll need to clone the Dub repo and install the dependencies.

<Steps>
  <Step title="Clone the repo">
    First, clone the [Dub repo](https://d.to/github) into a public GitHub repository. If you are planning to distribute the code or allow users to interact with the code remotely (e.g., as part of a hosted application), make sure to provide source access (including modifications) as required by the [AGPLv3 license](https://d.to/license).

    ```bash Terminal theme={null}
    git clone https://github.com/dubinc/dub.git
    ```
  </Step>

  <Step title="Install dependencies">
    Run the following command to install the dependencies:

    ```bash Terminal theme={null}
    pnpm i
    ```
  </Step>

  <Step title="Remove unnecessary files">
    Delete the `apps/web/vercel.json` file since cron jobs are not required for the self-hosted version:

    ```bash Terminal theme={null}
    rm apps/web/vercel.json
    ```
  </Step>

  <Step title="Set up environment variables">
    Convert the `.env.example` file to `.env`. You can start filling in the first few environment variables:

    ```bash Terminal theme={null}
    # The domain that your app will be hosted on
    NEXT_PUBLIC_APP_DOMAIN=acme.com
    # The short domain that your app will be using (could be the same as the above)
    NEXT_PUBLIC_APP_SHORT_DOMAIN=ac.me
    # The ID of the Vercel team that your app will be deployed to: https://vercel.com/docs/accounts/create-a-team#find-your-team-id
    TEAM_ID_VERCEL=
    # The unique access token for your Vercel account: https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token
    AUTH_BEARER_TOKEN=
    ```

    You will fill in the remaining environment variables in the following steps.
  </Step>
</Steps>

## Step 2: Set up Tinybird Clickhouse database

Next, you'll need to set up the [Tinybird](https://tinybird.co) Clickhouse database. This will be used to store time-series click events data.

<Steps>
  <Step title="Create Tinybird Workspace">
    In your [Tinybird](https://tinybird.co/) account, create a new Workspace.

    Copy your `admin` [Auth Token](https://www.tinybird.co/docs/concepts/auth-tokens.html). Paste this token as the `TINYBIRD_API_KEY` environment variable in your `.env` file.
  </Step>

  <Step title="Install Tinybird CLI and authenticate">
    In your newly-cloned Dub repo, navigate to the `packages/tinybird` directory.

    Install the Tinybird CLI with `pip install tinybird-cli` (requires Python >= 3.8).

    Run `tb login` and paste your `admin` Auth Token.
  </Step>

  <Step title="Publish Tinybird datasource and endpoints">
    Run `tb deploy` to publish the datasource and endpoints in the `packages/tinybird` directory. You should see the following output (truncated for brevity):

    ```bash Terminal theme={null}
    $ tb deploy

    ** Processing ./datasources/click_events.datasource
    ** Processing ./endpoints/clicks.pipe
    ...
    ** Building dependencies
    ** Running 'click_events'
    ** 'click_events' created
    ** Running 'device'
    ** => Test endpoint at https://api.us-east.tinybird.co/v0/pipes/device.json
    ** Token device_endpoint_read_8888 not found, creating one
    ** => Test endpoint with:
    ** $ curl https://api.us-east.tinybird.co/v0/pipes/device.json?token=p.ey...NWeaoTLM
    ** 'device' created
    ...
    ```
  </Step>

  <Step title="Set up Tinybird API base URL">
    You will then need to update your [Tinybird API base URL](https://www.tinybird.co/docs/api-reference/api-reference.html#regions-and-endpoints) to match the region of your database.

    From the previous step, take note of the **Test endpoint** URL. It should look something like this:

    ```bash Terminal theme={null}
    Test endpoint at https://api.us-east.tinybird.co/v0/pipes/device.json
    ```

    Copy the base URL and paste it as the `TINYBIRD_API_URL` environment variable in your `.env` file.

    ```bash Terminal theme={null}
    TINYBIRD_API_URL=https://api.us-east.tinybird.co
    ```
  </Step>
</Steps>

## Step 3: Set up Upstash Redis database

Next, you'll need to set up the [Upstash](https://upstash.com) Redis database. This will be used to cache link metadata and serve link redirects.

<Steps>
  <Step title="Create Upstash database">
    In your [Upstash account](https://console.upstash.com/), create a new database.

    For better performance & read times, we recommend setting up a global database with several read regions.

    <Frame>
      <img alt="Upstash Redis database" />
    </Frame>
  </Step>

  <Step title="Set up Upstash environment variables">
    Once your database is created, copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the **REST API** section into your `.env` file.

    <Frame>
      <img alt="Upstash Redis tokens" />
    </Frame>

    Navigate to the [QStash tab](https://console.upstash.com/qstash) and copy the `QSTASH_TOKEN`, `QSTASH_CURRENT_SIGNING_KEY`, and `QSTASH_NEXT_SIGNING_KEY` from the **Request Builder** section into your `.env` file.

    <Frame>
      <img alt="Upstash QStash tokens" />
    </Frame>
  </Step>
</Steps>

## Step 4: Set up PlanetScale MySQL database

Next, you'll need to set up a [PlanetScale](https://planetscale.com/)-compatible MySQL database. This will be used to store user data and link metadata.

<Note>
  PlanetScale recently [removed their free
  tier](https://planetscale.com/blog/planetscale-forever), so you'll need to pay
  for this option. A cheaper alternative is to use a [MySQL database on
  Railway](https://railway.app/template/mysql) (\$5/month).

  For [local development](/docs/local-development), we recommend using a [local MySQL database
  with PlanetScale simulator](/docs/local-development#option-1-local-mysql-database-with-planetscale-simulator-recommended) (100% free).
</Note>

<Steps>
  <Step title="Create PlanetScale database">
    In your [PlanetScale account](https://app.planetscale.com/), create a new database.

    Once your database is created, you'll be prompted to select your language or Framework. Select **Prisma**.

    <Frame>
      <img alt="PlanetScale choose framework" />
    </Frame>
  </Step>

  <Step title="Set up PlanetScale environment variables">
    Then, you'll have to create a new password for your database. Once the password is created, scroll down to the **Add credentials to .env** section and copy the `DATABASE_URL` into your `.env` file.

    <Frame>
      <img alt="PlanetScale add credentials" />
    </Frame>
  </Step>

  <Step title="Generate Prisma client and create database tables">
    In your Dub codebase, navigate to `apps/web/prisma/schema.prisma` and replace all the columns in the `DefaultDomains` model to the normalized version of your custom short domain (removing the `.` character).

    For example, if your custom short domain is `ac.me`, your `DefaultDomains` model should look like this:

    ```prisma apps/web/prisma/schema.prisma theme={null}
    model DefaultDomains {
      id          String   @id @default(cuid())
      acme        Boolean  @default(true)
      projectId   String   @unique
      project     Project  @relation(fields: [projectId], references: [id], onDelete: Cascade)
    }
    ```

    In the terminal, navigate to the `apps/web` directory and run the following command to generate the Prisma client:

    ```bash Terminal theme={null}
    pnpm run prisma:generate
    ```

    Then, create the database tables with the following command:

    ```bash Terminal theme={null}
    pnpm run prisma:push
    ```
  </Step>
</Steps>

## Step 5: Set up GitHub OAuth

Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to Dub with your GitHub account.

Don't forget to set the following Callback URLs:

* `https://app.acme.com/api/auth/callback/github`
* `http://localhost:8888/api/auth/callback/github` for local development.

<Info>
  Optional: Set the "Email addresses" account permission to **read-only** in
  order to access private email addresses on GitHub.
</Info>

Once your GitHub App is created, copy the `Client ID` and `Client Secret` into your `.env` file as the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` environment variables.

## Step 6: Set up Cloudflare R2

Dub stores user-generated assets in either S3 or S3-compatible services like [Cloudflare R2](https://cloudflare.com/r2). These include:

* Project logos
* User avatars
* [Custom Social Media Cards](/help/article/custom-link-previews) images

We recommend using [Cloudflare R2](https://cloudflare.com/r2) for self-hosting Dub, as it's a more cost-effective solution compared to AWS S3. Here's how you can set it up:

<Steps>
  <Step title="Create R2 bucket">
    <Note>You'll need to subscribe to the R2 service if you haven't already.</Note>

    In your [Cloudflare account](https://dash.cloudflare.com/), create a new R2 bucket. We recommend giving your bucket a descriptive name (e.g. `dubassets`) and leaving the remaining settings as is.

    <Frame>
      <img alt="Cloudflare R2 bucket" />
    </Frame>

    In your bucket settings, copy the **S3 API** value – you'll need it in Step 3.
  </Step>

  <Step title="Set up access to R2">
    From the R2 main page, click **Manage R2 API Tokens** on the right-hand column.

    <Frame>
      <img alt="Cloudflare manage API tokens" />
    </Frame>

    Then, click **Create API Token**.

    <Frame>
      <img alt="Cloudflare R2 API token" />
    </Frame>

    Make sure to name your API token something relevant to the service that will be using the token.

    Give it "Object Read & Write" permissions, and we recommend only applying ito to a single bucket.

    You can leave the remaining settings (TTL, Client IP Address Filtering) as is, and click **Create API Token**.

    After you create you token, copy the `Access Key ID` and `Secret Access Key` values – you'll need them in the next step.
  </Step>

  <Step title="Set up R2 environment variables">
    Once you have your credentials, set them in your `.env` file:

    ```TypeScript .env theme={null}
    STORAGE_ACCESS_KEY_ID= // this is the Access Key ID value from Step 2
    STORAGE_SECRET_ACCESS_KEY= // this is the Secret Access Key value from Step 2
    STORAGE_ENDPOINT= // this is the S3 API value from Step 1
    ```
  </Step>

  <Step title="Set up R2 domain">
    In order for your images to be publically accessible in R2 you need to setup a domain. You can either use your own domain or an R2.dev subdomain.

    To use your own domain, you'll need to create a CNAME record in your DNS settings that points to your R2 bucket.

    In you plan to use an R2.dev subdomain, make sure you "Allow Access".

    Then set the `STORAGE_BASE_URL` in your `.env` file to the domain you chose.

    ```bash theme={null}
    STORAGE_BASE_URL={URL your assets as available at} # https://static.example.com
    ```

    <Frame>
      <img alt="Cloudflare R2 domain" />
    </Frame>
  </Step>
</Steps>

## Step 7: Set up Resend (optional)

<Note>
  Note that if you want to use magic link sign-in, this is a required step.
</Note>

Next, you'll need to set up Resend for transactional emails (e.g. magic link emails):

1. Sign up for Resend and [create your API key here](https://resend.com/api-keys).
2. Copy the API key into your `.env` file as the `RESEND_API_KEY` environment variable.
3. You'll then need to set up and verify your domain by [following this guide here](https://resend.com/docs/dashboard/domains/introduction).

## Step 8: Set up Unsplash (optional)

Dub uses Unsplash's API for the [Custom Social Media Cards](/help/article/custom-link-previews) feature. You'll need to set up an Unsplash application to get an access key.

<Frame>
  ![Custom social media
  cards](https://assets.dub.co/changelog/custom-social-cards.png)
</Frame>

Check out Unsplash's [official documentation](https://unsplash.com/documentation#creating-a-developer-account) to learn how you can set up the `UNSPLASH_ACCESS_KEY` env var.

## Step 9: Deploy to Vercel

Once you've set up all of the above services, you can now deploy your app to Vercel.

<Steps>
  <Step title="Deploy code to GitHub">
    If you haven't already, push up your cloned repository to GitHub by running the following commands:

    ```bash Terminal theme={null}
    git add .
    git commit -m "Initial commit"
    git push origin main
    ```
  </Step>

  <Step title="Create a new Vercel project">
    In your [Vercel account](https://vercel.com/), create a new project. Then, select your GitHub repository and click **Import**.

    Make sure that your **Framework Preset** is set to **Next.js** and the **Root Directory** is set to `apps/web`.

    <Frame>
      <img alt="Vercel Framework Preset and Root Directory" />
    </Frame>

    In the **Environment Variables** section, add all of the environment variables from your `.env` file by copying all of them and pasting it into the first input field. A few notes:

    * Remove the `PROJECT_ID_VERCEL` environment variable for now since we will only get the project ID after deploying the project.
    * Replace the `NEXTAUTH_URL` environment variable with the app domain that you will be using (e.g. `https://app.acme.com`).

    Click on **Deploy** to deploy your project.

    <Tip>
      If you get a `No Output Directory called "public" was found after the build
                  completed` error, make sure that your [Vercel deployment
      settings](https://vercel.com/docs/deployments/configure-a-build) to make sure that they match the following:

      <Frame>
        <img alt="Vercel Deploy settings" />
      </Frame>
    </Tip>
  </Step>

  <Step title="Add required environment variables">
    Once the project deploys, retrieve your [Vercel project ID](https://vercel.com/docs/projects/overview#project-id) and add it as the `PROJECT_ID_VERCEL` environment variable – both in your `.env` file and in your newly created Vercel project's settings (under **Settings > Environment Variables**)

    Add both the `NEXT_PUBLIC_APP_DOMAIN` and `NEXT_PUBLIC_APP_SHORT_DOMAIN` as domains in your Vercel project's settings (under **Settings** > **Domains**). You can follow this guide to learn [how to set up a custom domain on Vercel](https://vercel.com/docs/projects/domains/add-a-domain).
  </Step>

  <Step title="Redeploy your Vercel project">
    Go back to the **Deployments** page and redeploy your project.

    Once the deployment is complete, you should be able to visit your app domain (e.g. `https://app.acme.com`) and see the following login page:

    <Frame>
      <img alt="Whitelabeled Login" />
    </Frame>
  </Step>
</Steps>

## Caveats

This guide is meant to be a starting point for self-hosting Dub. It currently depends on the following services to work:

* [Tinybird](https://www.tinybird.co/) for the analytics database
* [Upstash](https://upstash.com/) for the Redis database
* [PlanetScale](https://planetscale.com/) for the MySQL database
* [Vercel](https://vercel.com/) for hosting & [Edge Middleware](https://vercel.com/docs/functions/edge-middleware)

In the future, we plan to make it easier to self-host Dub by making these dependencies optional by swapping them out for native databases (e.g. mysql, redis, clickhouse, [GeoLite2](https://github.com/GitSquared/node-geolite2-redist) etc.)

Also, Docker is currently not supported, but we have a few [open](https://github.com/dubinc/dub/issues/25) [issues](https://github.com/dubinc/dub/issues/378) and [PRs](https://github.com/dubinc/dub/pull/391) for it.


# Event types
Source: https://dub.co/docs/webhooks/event-types

List of supported webhook event types and their payloads

Webhooks send real-time notifications when events happen in your Dub workspace. All webhook payloads follow this structure:

```json webhook-payload.json theme={null}
{
  "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
  "event": "link.created",
  "createdAt": "2024-08-26T16:41:52.346Z",
  "data": {}
}
```

* **id** – Unique event ID
* **event** – Event type (e.g. `link.created`)
* **createdAt** – ISO 8601 timestamp when the event was created
* **data** – Event-specific payload

## Dub Partners

[Dub Partners](https://dub.co/partners) lets you run a partner program with referral links, commissions, and payouts. Here are the webhook events that are available for Dub Partners:

| Event Type                                                                             | Description                                                                                                     |
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| [`partner.enrolled`](/docs/webhooks/events/partner-enrolled)                           | Occurs when a **new partner is enrolled** in your partner program.                                              |
| [`partner.application_submitted`](/docs/webhooks/events/partner-application-submitted) | Occurs when a **partner submits an application** to join your partner program.                                  |
| [`lead.created`](/docs/webhooks/events/lead-created)                                   | Occurs when a **new lead is tracked**.                                                                          |
| [`sale.created`](/docs/webhooks/events/sale-created)                                   | Occurs when a **new sale is tracked**.                                                                          |
| [`commission.created`](/docs/webhooks/events/commission-created)                       | Occurs when a **new commission is generated** — from a tracked conversion or created manually in the dashboard. |

## Dub Links

[Dub Links](https://dub.co/links) is your short link and analytics platform for creating, managing, and tracking links. Here are the webhook events that are available for Dub Links:

| Event Type                                           | Description                                              |
| ---------------------------------------------------- | -------------------------------------------------------- |
| [`link.created`](/docs/webhooks/events/link-created) | Occurs when a **new link is created** in your workspace. |
| [`link.updated`](/docs/webhooks/events/link-updated) | Occurs when a **link is updated** in your workspace.     |
| [`link.deleted`](/docs/webhooks/events/link-deleted) | Occurs when a **link is deleted** in your workspace.     |
| [`link.clicked`](/docs/webhooks/events/link-clicked) | Occurs when a **link is clicked** by a user.             |


# commission.created
Source: https://dub.co/docs/webhooks/events/commission-created



Event triggered when a [new commission is created](/docs/api-reference/commissions/list).

<Tip>
  When enabled, this webhook is triggered when a commission is created either
  from a tracked conversion or manually in the dashboard.

  Use the `userId` field to distinguish between manual commissions from automatic ones.
</Tip>

<WebhookResponseBodyParameters type="commission.created">
  <ParamField type="string">
    Unique identifier for the commission.
  </ParamField>

  <ParamField type="string">
    Commission type (e.g. <code>sale</code>).
  </ParamField>

  <ParamField type="number">
    Sale/order amount in cents.
  </ParamField>

  <ParamField type="number">
    Commission earnings in cents.
  </ParamField>

  <ParamField type="string">
    Currency code (e.g. <code>usd</code>).
  </ParamField>

  <ParamField type="string">
    Commission status (e.g. <code>pending</code>).
  </ParamField>

  <ParamField type="string | null">
    Related invoice ID, if any.
  </ParamField>

  <ParamField type="string | null">
    Optional description (e.g. for manual commissions).
  </ParamField>

  <ParamField type="number">
    Quantity (e.g. number of items).
  </ParamField>

  <ParamField type="string | null">
    ID of the user who created the commission. Set for manual commissions; null for automatic.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the commission was created.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the commission was last updated.
  </ParamField>

  <ParamField type="object">
    The partner that earned the commission (id, name, email, image, country, groupId, totalClicks, totalLeads, totalSales, totalSaleAmount, totalCommissions).
  </ParamField>

  <ParamField type="object | null">
    The customer associated with the conversion. Null for click-based or custom commissions.

    <Expandable title="customer object">
      <ParamField type="string">
        Unique identifier for the customer.
      </ParamField>

      <ParamField type="string">
        Your external customer ID, if provided when tracking.
      </ParamField>

      <ParamField type="string">
        Customer name.
      </ParamField>

      <ParamField type="string">
        Customer email address.
      </ParamField>

      <ParamField type="string | null">
        URL to the customer's avatar.
      </ParamField>

      <ParamField type="string">
        Country code.
      </ParamField>

      <ParamField type="number">
        Number of sales from this customer.
      </ParamField>

      <ParamField type="number">
        Total sale amount in cents.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the customer was first seen.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object | null">
    The referral link associated with the commission.

    <Expandable title="link object">
      <ParamField type="string">
        Link ID.
      </ParamField>

      <ParamField type="string">
        Full short link URL.
      </ParamField>

      <ParamField type="string">
        Short link domain.
      </ParamField>

      <ParamField type="string">
        Short link path/slug.
      </ParamField>
    </Expandable>
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_64dv6vxYVgltzJBKc9ujJ1ghL",
    "event": "commission.created",
    "createdAt": "2025-07-16T10:48:15.468Z",
    "data": {
      "id": "cm_1K09DJTBCRT24P6BRD515CK29",
      "type": "sale",
      "amount": 50000,
      "earnings": 10000,
      "currency": "usd",
      "status": "pending",
      "invoiceId": null,
      "description": null,
      "quantity": 1,
      "userId": "cludszk1h0000wmd2e0ea2b0p",
      "createdAt": "2025-07-16T10:48:14.722Z",
      "updatedAt": "2025-07-16T10:48:14.960Z",
      "partner": {
        "id": "pn_1K06X6FX2GRB31NCM2VVCGJ72",
        "name": "Matthew Hayden",
        "email": "matthew@example.com",
        "image": null,
        "payoutsEnabledAt": null,
        "country": "US",
        "groupId": "grp_1K6K3HD0QE7XTX5HSVR77AK5B",
        "totalClicks": 50,
        "totalLeads": 15,
        "totalConversions": 10,
        "totalSales": 10,
        "totalSaleAmount": 100000,
        "totalCommissions": 50000
      },
      "customer": {
        "id": "cus_1K09DJDEACR47NPYC93RM43WF",
        "externalId": "TaMD05AnuyqeI",
        "name": "David",
        "email": "david@example.com",
        "avatar": null,
        "country": "US",
        "sales": 1,
        "saleAmount": 50000,
        "createdAt": "2025-07-16T10:48:01.739Z"
      },
      "link": {
        "id": "cm0lcuvtz000xcutmqw4a7wi3",
        "shortLink": "https://dub.sh/track-test",
        "domain": "dub.sh",
        "key": "track-test"
      }
    }
  }
  ```
</ResponseExample>


# lead.created
Source: https://dub.co/docs/webhooks/events/lead-created



Event triggered when a [new lead is tracked](/docs/api-reference/track/lead).

<WebhookResponseBodyParameters type="lead.created">
  <ParamField type="string">
    Name of the tracked event (e.g. <code>Sign up</code>).
  </ParamField>

  <ParamField type="object">
    The customer that signed up.

    <Expandable title="customer object">
      <ParamField type="string">
        Unique identifier for the customer.
      </ParamField>

      <ParamField type="string">
        Customer name.
      </ParamField>

      <ParamField type="string">
        Customer email address.
      </ParamField>

      <ParamField type="string | null">
        URL to the customer's avatar image.
      </ParamField>

      <ParamField type="string">
        Your external customer ID, if provided when tracking.
      </ParamField>

      <ParamField type="string | null">
        The customer's Stripe customer ID, if linked.
      </ParamField>

      <ParamField type="number | null">
        Total number of sales for the customer.
      </ParamField>

      <ParamField type="number | null">
        Total sale amount for the customer in cents.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the customer was first seen.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp of the customer's first sale.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp when the customer canceled their subscription, if applicable.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The click event that led to the lead.

    <Expandable title="click object">
      <ParamField type="string">
        Unique identifier for the click.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the click occurred.
      </ParamField>

      <ParamField type="string">
        The destination URL that was visited.
      </ParamField>

      <ParamField type="string">
        Country code (e.g. <code>US</code>).
      </ParamField>

      <ParamField type="string">
        City name.
      </ParamField>

      <ParamField type="string">
        Region or region code.
      </ParamField>

      <ParamField type="string">
        Continent code (e.g. <code>NA</code>).
      </ParamField>

      <ParamField type="string">
        Device type (e.g. <code>Desktop</code>, <code>Mobile</code>).
      </ParamField>

      <ParamField type="string">
        Browser name (e.g. <code>Chrome</code>).
      </ParamField>

      <ParamField type="string">
        Operating system (e.g. <code>Mac OS</code>).
      </ParamField>

      <ParamField type="string">
        User agent string.
      </ParamField>

      <ParamField type="string">
        Referrer hostname or <code>(direct)</code>.
      </ParamField>

      <ParamField type="string">
        Full referrer URL or <code>(direct)</code>.
      </ParamField>

      <ParamField type="boolean">
        Whether the click came from a QR code scan.
      </ParamField>

      <ParamField type="string">
        IP address of the visitor.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The referral link the lead is associated with (full link object).
  </ParamField>

  <ParamField type="object | null">
    Partner details. Only present when the link is a partner referral link (id, name, email, image, country, groupId, totalClicks, totalLeads, totalConversions, totalSales, totalSaleAmount, totalCommissions, etc.).
  </ParamField>

  <ParamField type="object | null">
    Optional metadata associated with the lead event.
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_P343bmyae40ALQYr5HT4vRXRd",
    "event": "lead.created",
    "createdAt": "2024-08-30T09:53:50.343Z",
    "data": {
      "eventName": "Sign up",
      "customer": {
        "id": "oU5P0SqI8fpwx5bxw1",
        "name": "John",
        "email": "john@example.com",
        "avatar": "https://example.com/john.jpeg",
        "externalId": "",
        "stripeCustomerId": null,
        "sales": 0,
        "saleAmount": 0,
        "createdAt": "2024-08-30T09:53:50.343Z",
        "firstSaleAt": null,
        "subscriptionCanceledAt": null
      },
      "click": {
        "id": "d0UtZqE0BZuBPrJS",
        "timestamp": "2024-08-30T09:53:50.343Z",
        "url": "https://github.com/dubinc/dub",
        "country": "US",
        "city": "San Francisco",
        "region": "CA",
        "continent": "NA",
        "device": "Desktop",
        "browser": "Chrome",
        "os": "Mac OS",
        "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
        "referer": "(direct)",
        "refererUrl": "(direct)",
        "qr": false,
        "ip": "63.141.57.109"
      },
      "link": {
        "id": "cm0faqkyn0001txvfwjfeq7gl",
        "domain": "dub.sh",
        "key": "79ys3WA",
        "url": "https://github.com/dubinc/dub",
        "trackConversion": true,
        "externalId": null,
        "tenantId": null,
        "partnerId": null,
        "programId": null,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "disabledAt": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "folderId": null,
        "tagId": null,
        "tags": [],
        "webhookIds": [],
        "comments": null,
        "shortLink": "https://dub.sh/79ys3WA",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 10,
        "lastClicked": "2024-08-30T07:45:09.000Z",
        "leads": 5,
        "sales": 0,
        "saleAmount": 0,
        "createdAt": "2024-08-29T13:03:59.098Z",
        "updatedAt": "2024-08-30T09:53:49.505Z",
        "projectId": null,
        "testVariants": null,
        "testCompletedAt": null,
        "testStartedAt": null
      },
      "partner": {
        "id": "pn_1JRB6678XHGBZE95R5PH5QVGS",
        "name": "John Smith",
        "email": "john@partner.com",
        "image": "https://example.com/avatar.jpg",
        "payoutsEnabledAt": null,
        "country": "US",
        "groupId": "grp_1K6K3HD0QE7XTX5HSVR77AK5B",
        "totalClicks": 150,
        "totalLeads": 25,
        "totalConversions": 15,
        "totalSales": 10,
        "totalSaleAmount": 50000,
        "totalCommissions": 5000
      },
      "metadata": null
    }
  }
  ```
</ResponseExample>


# link.clicked
Source: https://dub.co/docs/webhooks/events/link-clicked



Event triggered when a link is clicked by a user.

<Tip>
  You can scope which links trigger a `link.clicked` webhook. When creating or editing the webhook in the dashboard, choose one of the following:

  1. **All links** – trigger the webhook for every link in your workspace.
  2. **Include specific folders** (recommended) – trigger the webhook for all links in the selected folders.
  3. **Include specific links** – trigger the webhook only for the links you explicitly select.

  If you select the 3rd option, you can programmatically associate specific links with your `link.clicked` webhook by passing the webhook ID under the `webhookIds` [prop](/docs/api-reference/links/create#body-webhook-ids-one-of-0) when creating or updating a link.
</Tip>

<WebhookResponseBodyParameters type="link.clicked">
  <ParamField type="object">
    Details about the click event.

    <Expandable title="click object">
      <ParamField type="string">
        Unique identifier for the click.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the click occurred.
      </ParamField>

      <ParamField type="string">
        The destination URL that was visited.
      </ParamField>

      <ParamField type="string">
        Country code (e.g. <code>US</code>).
      </ParamField>

      <ParamField type="string">
        City name.
      </ParamField>

      <ParamField type="string">
        Region or region code.
      </ParamField>

      <ParamField type="string">
        Continent code (e.g. <code>NA</code>).
      </ParamField>

      <ParamField type="string">
        Device type (e.g. <code>Desktop</code>, <code>Mobile</code>).
      </ParamField>

      <ParamField type="string">
        Browser name (e.g. <code>Chrome</code>).
      </ParamField>

      <ParamField type="string">
        Operating system (e.g. <code>Mac OS</code>).
      </ParamField>

      <ParamField type="string">
        User agent string.
      </ParamField>

      <ParamField type="string">
        Referrer hostname or <code>(direct)</code>.
      </ParamField>

      <ParamField type="string">
        Full referrer URL or <code>(direct)</code>.
      </ParamField>

      <ParamField type="boolean">
        Whether the click came from a QR code scan.
      </ParamField>

      <ParamField type="string">
        IP address of the visitor.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The link resource that was clicked (full link object including stats such as <code>clicks</code>, <code>shortLink</code>, <code>url</code>, etc.).
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_b9ywgxWqai2glUpCQjclB17kM",
    "event": "link.clicked",
    "createdAt": "2024-08-30T10:16:13.149Z",
    "data": {
      "click": {
        "id": "d0UtZqE0BZuBPrJS",
        "timestamp": "2024-08-30T10:16:12.124Z",
        "url": "https://github.com/dubinc/dub",
        "country": "US",
        "city": "San Francisco",
        "region": "CA",
        "continent": "NA",
        "device": "Desktop",
        "browser": "Chrome",
        "os": "Mac OS",
        "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
        "referer": "(direct)",
        "refererUrl": "(direct)",
        "qr": false,
        "ip": "63.141.57.109"
      },
      "link": {
        "id": "cm0faqkyn0001txvfwjfeq7gl",
        "domain": "dub.sh",
        "key": "79ys3WA",
        "url": "https://github.com/dubinc/dub",
        "trackConversion": true,
        "externalId": null,
        "tenantId": null,
        "partnerId": null,
        "programId": null,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "disabledAt": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "folderId": null,
        "tagId": null,
        "tags": [],
        "webhookIds": [],
        "comments": null,
        "shortLink": "https://dub.sh/79ys3WA",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 11,
        "lastClicked": "2024-08-30T07:45:09.000Z",
        "leads": 6,
        "sales": 10,
        "saleAmount": 200000,
        "createdAt": "2024-08-29T13:03:59.098Z",
        "updatedAt": "2024-08-30T10:16:12.126Z",
        "projectId": null,
        "testVariants": null,
        "testCompletedAt": null,
        "testStartedAt": null
      }
    }
  }
  ```
</ResponseExample>


# link.created
Source: https://dub.co/docs/webhooks/events/link-created



Event triggered when a [new link is created](/docs/api-reference/links/create).

<WebhookResponseBodyParameters type="link.created">
  <ParamField type="string">
    Unique identifier for the link.
  </ParamField>

  <ParamField type="string">
    The short link domain (e.g. <code>dub.sh</code>).
  </ParamField>

  <ParamField type="string">
    The short link path/slug.
  </ParamField>

  <ParamField type="string">
    The destination URL.
  </ParamField>

  <ParamField type="string">
    Full short link URL.
  </ParamField>

  <ParamField type="string">
    URL to the QR code image for this link.
  </ParamField>

  <ParamField type="string">
    ID of the user who created the link.
  </ParamField>

  <ParamField type="string">
    ID of the workspace the link belongs to.
  </ParamField>

  <ParamField type="number">
    Total click count for the link.
  </ParamField>

  <ParamField type="number">
    Total lead count (when conversion tracking is enabled).
  </ParamField>

  <ParamField type="number">
    Total sale count (when conversion tracking is enabled).
  </ParamField>

  <ParamField type="number">
    Total sale amount in cents.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the link was created.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the link was last updated.
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
    "event": "link.created",
    "createdAt": "2024-08-26T16:41:52.346Z",
    "data": {
      "id": "cm0b87844000dismqhkviju54",
      "domain": "dub.sh",
      "key": "sOvvXDT",
      "url": "https://github.com/stack-auth/stack",
      "trackConversion": false,
      "externalId": null,
      "tenantId": null,
      "partnerId": null,
      "programId": null,
      "archived": false,
      "expiresAt": null,
      "expiredUrl": null,
      "disabledAt": null,
      "password": null,
      "proxy": false,
      "title": null,
      "description": null,
      "image": null,
      "video": null,
      "rewrite": false,
      "doIndex": false,
      "ios": null,
      "android": null,
      "geo": null,
      "publicStats": false,
      "folderId": null,
      "tagId": null,
      "tags": [],
      "webhookIds": [],
      "comments": null,
      "shortLink": "https://dub.sh/sOvvXDT",
      "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1",
      "utm_source": null,
      "utm_medium": null,
      "utm_campaign": null,
      "utm_term": null,
      "utm_content": null,
      "userId": "cm022rkcw0000ikt14mscg9sg",
      "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
      "clicks": 0,
      "lastClicked": null,
      "leads": 0,
      "sales": 0,
      "saleAmount": 0,
      "createdAt": "2024-08-26T16:41:52.084Z",
      "updatedAt": "2024-08-26T16:41:52.084Z",
      "projectId": "cm022sis60003ikt1syy7kfhl",
      "testVariants": null,
      "testCompletedAt": null,
      "testStartedAt": null
    }
  }
  ```
</ResponseExample>


# link.deleted
Source: https://dub.co/docs/webhooks/events/link-deleted



Event triggered when a [link is deleted](/docs/api-reference/links/delete).

<WebhookResponseBodyParameters type="link.deleted">
  <ParamField type="string">
    Unique identifier for the link.
  </ParamField>

  <ParamField type="string">
    The short link domain (e.g. <code>dub.sh</code>).
  </ParamField>

  <ParamField type="string">
    The short link path/slug.
  </ParamField>

  <ParamField type="string">
    The destination URL.
  </ParamField>

  <ParamField type="string">
    Full short link URL.
  </ParamField>

  <ParamField type="string">
    ID of the user who owned the link.
  </ParamField>

  <ParamField type="string">
    ID of the workspace the link belonged to.
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
    "event": "link.deleted",
    "createdAt": "2024-08-26T16:41:52.346Z",
    "data": {
      "id": "cm0b87844000dismqhkviju54",
      "domain": "dub.sh",
      "key": "sOvvXDT",
      "url": "https://github.com/stack-auth/stack",
      "trackConversion": false,
      "externalId": null,
      "tenantId": null,
      "partnerId": null,
      "programId": null,
      "archived": false,
      "expiresAt": null,
      "expiredUrl": null,
      "disabledAt": null,
      "password": null,
      "proxy": false,
      "title": null,
      "description": null,
      "image": null,
      "video": null,
      "rewrite": false,
      "doIndex": false,
      "ios": null,
      "android": null,
      "geo": null,
      "publicStats": false,
      "folderId": null,
      "tagId": null,
      "tags": [],
      "webhookIds": [],
      "comments": null,
      "shortLink": "https://dub.sh/sOvvXDT",
      "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1",
      "utm_source": null,
      "utm_medium": null,
      "utm_campaign": null,
      "utm_term": null,
      "utm_content": null,
      "userId": "cm022rkcw0000ikt14mscg9sg",
      "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
      "clicks": 0,
      "lastClicked": null,
      "leads": 0,
      "sales": 0,
      "saleAmount": 0,
      "createdAt": "2024-08-26T16:41:52.084Z",
      "updatedAt": "2024-08-26T16:41:52.084Z",
      "projectId": "cm022sis60003ikt1syy7kfhl",
      "testVariants": null,
      "testCompletedAt": null,
      "testStartedAt": null
    }
  }
  ```
</ResponseExample>


# link.updated
Source: https://dub.co/docs/webhooks/events/link-updated



Event triggered when a [link is updated](/docs/api-reference/links/update).

<WebhookResponseBodyParameters type="link.updated">
  <ParamField type="string">
    Unique identifier for the link.
  </ParamField>

  <ParamField type="string">
    The short link domain (e.g. <code>dub.sh</code>).
  </ParamField>

  <ParamField type="string">
    The short link path/slug.
  </ParamField>

  <ParamField type="string">
    The destination URL.
  </ParamField>

  <ParamField type="string">
    Full short link URL.
  </ParamField>

  <ParamField type="string">
    URL to the QR code image for this link.
  </ParamField>

  <ParamField type="string">
    ID of the user who owns the link.
  </ParamField>

  <ParamField type="string">
    ID of the workspace the link belongs to.
  </ParamField>

  <ParamField type="number">
    Total click count for the link.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the link was created.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the link was last updated.
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
    "event": "link.updated",
    "createdAt": "2024-08-26T16:41:52.346Z",
    "data": {
      "id": "cm0b87844000dismqhkviju54",
      "domain": "dub.sh",
      "key": "sOvvXDT",
      "url": "https://github.com/stack-auth/stack",
      "trackConversion": false,
      "externalId": null,
      "tenantId": null,
      "partnerId": null,
      "programId": null,
      "archived": false,
      "expiresAt": null,
      "expiredUrl": null,
      "disabledAt": null,
      "password": null,
      "proxy": false,
      "title": null,
      "description": null,
      "image": null,
      "video": null,
      "rewrite": false,
      "doIndex": false,
      "ios": null,
      "android": null,
      "geo": null,
      "publicStats": false,
      "folderId": null,
      "tagId": null,
      "tags": [],
      "webhookIds": [],
      "comments": null,
      "shortLink": "https://dub.sh/sOvvXDT",
      "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1",
      "utm_source": null,
      "utm_medium": null,
      "utm_campaign": null,
      "utm_term": null,
      "utm_content": null,
      "userId": "cm022rkcw0000ikt14mscg9sg",
      "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
      "clicks": 0,
      "lastClicked": null,
      "leads": 0,
      "sales": 0,
      "saleAmount": 0,
      "createdAt": "2024-08-26T16:41:52.084Z",
      "updatedAt": "2024-08-26T16:41:52.084Z",
      "projectId": "cm022sis60003ikt1syy7kfhl",
      "testVariants": null,
      "testCompletedAt": null,
      "testStartedAt": null
    }
  }
  ```
</ResponseExample>


# partner.application_submitted
Source: https://dub.co/docs/webhooks/events/partner-application-submitted



Event triggered when a partner submits an application to join your partner program.

<WebhookResponseBodyParameters type="partner.application_submitted">
  <ParamField type="string">
    The application ID.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the application was submitted.
  </ParamField>

  <ParamField type="object">
    The partner who submitted the application.

    <Expandable title="partner object">
      <ParamField type="string">
        Unique identifier for the partner.
      </ParamField>

      <ParamField type="string">
        Partner name.
      </ParamField>

      <ParamField type="string">
        Partner email address.
      </ParamField>

      <ParamField type="string | null">
        Company name, if provided.
      </ParamField>

      <ParamField type="string | null">
        URL to the partner's profile image.
      </ParamField>

      <ParamField type="string | null">
        Partner bio or description.
      </ParamField>

      <ParamField type="string">
        Country code (e.g. <code>US</code>).
      </ParamField>

      <ParamField type="string">
        ID of the partner group they applied to.
      </ParamField>

      <ParamField type="string">
        Application status (e.g. <code>pending</code>).
      </ParamField>

      <ParamField type="string | null">
        Partner website URL.
      </ParamField>

      <ParamField type="string | null">
        YouTube profile URL.
      </ParamField>

      <ParamField type="string | null">
        Twitter/X profile URL.
      </ParamField>

      <ParamField type="string | null">
        LinkedIn profile URL.
      </ParamField>

      <ParamField type="string | null">
        Instagram profile URL.
      </ParamField>

      <ParamField type="string | null">
        TikTok profile URL.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="array">
    Array of form fields submitted by the partner.

    <Expandable title="form field object">
      <ParamField type="string">
        The form field label.
      </ParamField>

      <ParamField type="string | null">
        The value submitted for this field.
      </ParamField>
    </Expandable>
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
    "event": "partner.application_submitted",
    "createdAt": "2025-11-06T11:25:59.264Z",
    "data": {
      "id": "pga_1K9CEN4JWYACNHS4DR3PWNR2F",
      "createdAt": "2025-11-06T11:25:59.264Z",
      "partner": {
        "id": "pn_1K9BZE1K285BSTX4W6MPKXJFZ",
        "name": "Matthew Hayden",
        "email": "matthew@example.com",
        "companyName": null,
        "image": null,
        "description": "I'm a content creator who works with brands to grow their business.",
        "country": "US",
        "groupId": "grp_1K9BZE1K2RWYAWB2K1YN5TY7F",
        "status": "pending",
        "website": null,
        "youtube": null,
        "twitter": null,
        "linkedin": null,
        "instagram": null,
        "tiktok": null
      },
      "applicationFormData": [
        {
          "label": "Website",
          "value": "https://example.com/"
        },
        {
          "label": "How do you plan to promote Acme?",
          "value": "I'll promote Acme by sharing it on my social platforms and writing a blog post."
        },
        {
          "label": "Any additional questions or comments?",
          "value": null
        }
      ]
    }
  }
  ```
</ResponseExample>


# partner.enrolled
Source: https://dub.co/docs/webhooks/events/partner-enrolled



Event triggered when a [new partner is enrolled](/docs/api-reference/partners/create) in your partner program.

<WebhookResponseBodyParameters type="partner.enrolled">
  <ParamField type="string">
    Unique identifier for the partner.
  </ParamField>

  <ParamField type="string">
    Partner name.
  </ParamField>

  <ParamField type="string | null">
    Partner username.
  </ParamField>

  <ParamField type="string">
    Partner email address.
  </ParamField>

  <ParamField type="string | null">
    URL to the partner's profile image.
  </ParamField>

  <ParamField type="string | null">
    Partner bio or description.
  </ParamField>

  <ParamField type="string">
    Country code (e.g. <code>US</code>).
  </ParamField>

  <ParamField type="string | null">
    Partner company name, if provided.
  </ParamField>

  <ParamField type="string | null">
    Your external system's identifier for this partner.
  </ParamField>

  <ParamField type="string">
    ID of the partner program.
  </ParamField>

  <ParamField type="string">
    Partner ID (same as <code>id</code>).
  </ParamField>

  <ParamField type="string | null">
    Partner group ID.
  </ParamField>

  <ParamField type="string">
    Partner status (e.g. <code>approved</code>).
  </ParamField>

  <ParamField type="string | null">
    ISO 8601 timestamp when payouts were enabled for this partner.
  </ParamField>

  <ParamField type="string">
    ISO 8601 timestamp when the partner was enrolled.
  </ParamField>

  <ParamField type="number">
    Total clicks across the partner's links.
  </ParamField>

  <ParamField type="number">
    Total leads attributed to the partner.
  </ParamField>

  <ParamField type="number">
    Total conversions attributed to the partner.
  </ParamField>

  <ParamField type="number">
    Total sales attributed to the partner.
  </ParamField>

  <ParamField type="number">
    Total sale amount in cents.
  </ParamField>

  <ParamField type="number">
    Total commissions in cents.
  </ParamField>

  <ParamField type="string | null">
    Partner website URL.
  </ParamField>

  <ParamField type="array">
    Array of the partner's referral links.

    <Expandable title="link object">
      <ParamField type="string">
        Link ID.
      </ParamField>

      <ParamField type="string">
        Short link domain.
      </ParamField>

      <ParamField type="string">
        Short link path/slug.
      </ParamField>

      <ParamField type="string">
        Full short link URL.
      </ParamField>

      <ParamField type="string">
        Destination URL.
      </ParamField>

      <ParamField type="number">
        Click count for this link.
      </ParamField>

      <ParamField type="number">
        Lead count for this link.
      </ParamField>

      <ParamField type="number">
        Conversion count for this link.
      </ParamField>

      <ParamField type="number">
        Sale count for this link.
      </ParamField>

      <ParamField type="number">
        Sale amount in cents for this link.
      </ParamField>
    </Expandable>
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_ovabfqva8oqZzmLPN1JnwIfdt",
    "event": "partner.enrolled",
    "createdAt": "2025-04-08T17:11:56.492Z",
    "data": {
      "id": "pn_1JRB6678XHGBZE95R5PH5QVGS",
      "name": "Asleep Pink Mammal",
      "username": null,
      "email": "chosen.blush.barracuda@dub-internal-test.com",
      "image": "https://api.dub.co/og/avatar?seed=Asleep Pink Mammal",
      "country": "US",
      "companyName": null,
      "description": null,
      "networkStatus": "draft",
      "tenantId": null,
      "programId": "prog_CYCu7IMAapjkRpTnr8F1azjN",
      "partnerId": "pn_1JRB6678XHGBZE95R5PH5QVGS",
      "groupId": null,
      "status": "approved",
      "defaultPayoutMethod": null,
      "paypalEmail": null,
      "stripeConnectId": null,
      "payoutsEnabledAt": null,
      "identityVerifiedAt": null,
      "trustedAt": null,
      "createdAt": "2025-04-08T17:11:56.446Z",
      "applicationId": null,
      "website": "https://example.com",
      "youtube": null,
      "twitter": null,
      "linkedin": null,
      "instagram": null,
      "tiktok": null,
      "totalCommissions": 0,
      "clickRewardId": null,
      "leadRewardId": null,
      "saleRewardId": null,
      "referralRewardId": null,
      "discountId": null,
      "bannedAt": null,
      "bannedReason": null,
      "totalClicks": 0,
      "totalLeads": 0,
      "totalConversions": 0,
      "totalSales": 0,
      "totalSaleAmount": 0,
      "netRevenue": 0,
      "earningsPerClick": null,
      "averageLifetimeValue": null,
      "clickToLeadRate": null,
      "clickToConversionRate": null,
      "leadToConversionRate": null,
      "returnOnAdSpend": null,
      "links": [
        {
          "id": "link_1JRB6677YXQB49RC1HKH7TPJE",
          "domain": "getacme.link",
          "key": "uvYO5pMIpctKdUVJlL3jIL4o",
          "shortLink": "https://getacme.link/uvYO5pMIpctKdUVJlL3jIL4o",
          "url": "https://acme.com",
          "clicks": 0,
          "leads": 0,
          "conversions": 0,
          "sales": 0,
          "saleAmount": 0
        }
      ]
    }
  }
  ```
</ResponseExample>


# sale.created
Source: https://dub.co/docs/webhooks/events/sale-created



Event triggered when a [new sale is tracked](/docs/api-reference/track/sale).

<WebhookResponseBodyParameters type="sale.created">
  <ParamField type="string">
    Name of the tracked event (e.g. <code>Purchased</code>).
  </ParamField>

  <ParamField type="object">
    The customer that made the purchase.

    <Expandable title="customer object">
      <ParamField type="string">
        Unique identifier for the customer.
      </ParamField>

      <ParamField type="string">
        Customer name.
      </ParamField>

      <ParamField type="string">
        Customer email address.
      </ParamField>

      <ParamField type="string | null">
        URL to the customer's avatar image.
      </ParamField>

      <ParamField type="string">
        Your external customer ID, if provided when tracking.
      </ParamField>

      <ParamField type="string | null">
        The customer's Stripe customer ID, if linked.
      </ParamField>

      <ParamField type="number | null">
        Total number of sales for the customer.
      </ParamField>

      <ParamField type="number | null">
        Total sale amount for the customer in cents.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the customer was first seen.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp of the customer's first sale.
      </ParamField>

      <ParamField type="string | null">
        ISO 8601 timestamp when the customer canceled their subscription, if applicable.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The click event that led to the sale.

    <Expandable title="click object">
      <ParamField type="string">
        Unique identifier for the click.
      </ParamField>

      <ParamField type="string">
        ISO 8601 timestamp when the click occurred.
      </ParamField>

      <ParamField type="string">
        The destination URL that was visited.
      </ParamField>

      <ParamField type="string">
        Country code (e.g. <code>US</code>).
      </ParamField>

      <ParamField type="string">
        City name.
      </ParamField>

      <ParamField type="string">
        Region or region code.
      </ParamField>

      <ParamField type="string">
        Continent code (e.g. <code>NA</code>).
      </ParamField>

      <ParamField type="string">
        Device type (e.g. <code>Desktop</code>, <code>Mobile</code>).
      </ParamField>

      <ParamField type="string">
        Browser name (e.g. <code>Chrome</code>).
      </ParamField>

      <ParamField type="string">
        Operating system (e.g. <code>Mac OS</code>).
      </ParamField>

      <ParamField type="string">
        User agent string.
      </ParamField>

      <ParamField type="string">
        Referrer hostname or <code>(direct)</code>.
      </ParamField>

      <ParamField type="string">
        Full referrer URL or <code>(direct)</code>.
      </ParamField>

      <ParamField type="boolean">
        Whether the click came from a QR code scan.
      </ParamField>

      <ParamField type="string">
        IP address of the visitor.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object">
    The referral link the sale is associated with (full link object).
  </ParamField>

  <ParamField type="object | null">
    Partner details. Only present when the link is a partner referral link.
  </ParamField>

  <ParamField type="object">
    Details about the recorded sale.

    <Expandable title="sale object">
      <ParamField type="number">
        Sale amount in cents.
      </ParamField>

      <ParamField type="string">
        Currency code (e.g. <code>usd</code>).
      </ParamField>

      <ParamField type="string">
        Payment processor used (e.g. <code>stripe</code>).
      </ParamField>

      <ParamField type="string | null">
        External invoice ID, if any.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField type="object | null">
    Optional metadata associated with the sale event.
  </ParamField>
</WebhookResponseBodyParameters>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "evt_WHjyHhqsfYOrlJOOVJSoHXysD",
    "event": "sale.created",
    "createdAt": "2024-08-30T09:57:51.245Z",
    "data": {
      "eventName": "Purchased",
      "customer": {
        "id": "cm0gjdvr20001dkbha2n9gt2b",
        "name": "John",
        "email": "john@example.com",
        "avatar": "https://example.com/john.jpeg",
        "externalId": "",
        "stripeCustomerId": null,
        "sales": 0,
        "saleAmount": 0,
        "createdAt": "2024-08-30T09:57:51.245Z",
        "firstSaleAt": null,
        "subscriptionCanceledAt": null
      },
      "click": {
        "id": "d0UtZqE0BZuBPrJS",
        "timestamp": "2024-08-30T09:57:51.245Z",
        "url": "https://github.com/dubinc/dub",
        "country": "US",
        "city": "San Francisco",
        "region": "CA",
        "continent": "NA",
        "device": "Desktop",
        "browser": "Chrome",
        "os": "Mac OS",
        "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
        "referer": "(direct)",
        "refererUrl": "(direct)",
        "qr": false,
        "ip": "63.141.57.109"
      },
      "link": {
        "id": "cm0faqkyn0001txvfwjfeq7gl",
        "domain": "dub.sh",
        "key": "79ys3WA",
        "url": "https://github.com/dubinc/dub",
        "trackConversion": true,
        "externalId": null,
        "tenantId": null,
        "partnerId": null,
        "programId": null,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "disabledAt": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "folderId": null,
        "tagId": null,
        "tags": [],
        "webhookIds": [],
        "comments": null,
        "shortLink": "https://dub.sh/79ys3WA",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 10,
        "lastClicked": "2024-08-30T07:45:09.000Z",
        "leads": 5,
        "sales": 1,
        "saleAmount": 20000,
        "createdAt": "2024-08-29T13:03:59.098Z",
        "updatedAt": "2024-08-30T09:57:50.891Z",
        "projectId": null,
        "testVariants": null,
        "testCompletedAt": null,
        "testStartedAt": null
      },
      "partner": {
        "id": "pn_1JRB6678XHGBZE95R5PH5QVGS",
        "name": "Sarah Johnson",
        "email": "sarah@partner.com",
        "image": "https://example.com/sarah-avatar.jpg",
        "payoutsEnabledAt": null,
        "country": "US",
        "groupId": "grp_1K6K3HD0QE7XTX5HSVR77AK5B",
        "totalClicks": 200,
        "totalLeads": 30,
        "totalConversions": 20,
        "totalSales": 15,
        "totalSaleAmount": 75000,
        "totalCommissions": 7500
      },
      "sale": {
        "amount": 4500,
        "currency": "usd",
        "paymentProcessor": "stripe",
        "invoiceId": null
      },
      "metadata": null
    }
  }
  ```
</ResponseExample>


# Introduction
Source: https://dub.co/docs/webhooks/introduction

Use webhooks to get real-time notifications on events happening across your Dub workspace.

Webhooks allows you to listen to real-time events happening across your Dub workspace. With webhooks, you can build custom integrations with Dub, such as:

* Triggering a Zap on [Zapier](https://dub.co/integrations/zapier) when a new link is created in Dub
* Sending click events in real-time to [Segment](https://dub.co/integrations/segment) for further processing
* Get a [Slack](https://dub.co/integrations/slack) notification when someone clicks on your pitch deck link
* Building gamified referral programs – e.g. increment usage credits for the referrer when a [new signup](/docs/conversions/leads) happens

<Warning>
  The following endpoints do not trigger webhook events: [Bulk create
  links](/docs/api-reference/links/bulk-create), [Bulk update
  links](/docs/api-reference/links/bulk-update), [Bulk delete
  links](/docs/api-reference/links/bulk-delete).
</Warning>

In this guide, we'll show you how to configure webhooks for your Dub workspace and a list of available events you can listen to.

## Creating a webhook

To create a webhook for your Dub workspace, you'll need to follow these steps:

Navigate to the [**Webhooks** settings page](https://app.dub.co/webhooks) in your Dub workspace.

<Frame>
  <img alt="Create Webhook" />
</Frame>

Click on **Create Webhook** to create a new webhook.

<Frame>
  <img alt="Create Webhook Form" />
</Frame>

Fill in the required fields in the webhook creation form:

1. **Name**: Give your webhook a name that helps you identify it.
2. **URL**: Enter the URL of the endpoint where you want to send the webhook. We recommend using [webhook.site](https://webhook.site/) to test your webhook.
3. **Signing secret**: This is an auto-generated secret key that you can use to verify the authenticity of the webhook in your application. Learn more about [verifying webhook requests](/docs/webhooks/verify-webhook-requests).
4. **Events**: Select the events you want to listen to. You can select multiple events. Refer to the [Event Types](/docs/webhooks/event-types) section to see the list of available events.

Finally, click on **Create webhook** to create the webhook.

## Viewing webhook event logs

We also provide you with a webhook event logs page where you can view all the webhook events that have been sent to your webhook endpoint in real-time.

To view the webhook event logs, select the webhook from the [**Webhooks** settings page](https://app.dub.co/webhooks) and click on the **Webhook Logs** tab.

Here, you'll see a list of all the webhook events that have been sent to your webhook endpoint:

<Frame>
  <img alt="Webhook Event Logs" />
</Frame>

You can also select on a specific event, which will open up a sheet with more details about the event:

<Frame>
  <img alt="Webhook Event Logs Details" />
</Frame>

## Sending test events

You can send test events to your webhook URL to ensure that it's working correctly. To do this:

<Steps>
  <Step title="Go to the webhook details page">
    Navigate to the [**Webhooks** settings page](https://app.dub.co/webhooks) and select the webhook you want to test.

    Click on the **Update Details** tab to open the webhook details page.
  </Step>

  <Step title="Open the test event modal">
    Select the `⋮` icon on the top right of the page, and click on **Send test event**.

    <Frame>
      <img alt="Send Test Event" />
    </Frame>

    This will open up a modal where you can select the event you want to send.

    <Frame>
      <img alt="Send Test Event Modal" />
    </Frame>
  </Step>

  <Step title="Send the test event">
    Select the event you want to send, and click on **Send test webhook**.

    <Frame>
      <img alt="Send Test Webhook" />
    </Frame>

    You'll see a success message and receive the webhook event in the webhook endpoint you specified.
  </Step>
</Steps>

## Retry Behaviour

If your webhook endpoint does not respond with a success status code (2XX), we retry the request to ensure every message will be delivered. You can see all the retry attempts in your webhook event logs.

Webhooks are retried until they are successfully delivered – with an exponential backoff to avoid overwhelming your webhook endpoint (also known as the "[thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem)"). The delay is capped at 24 hours from the 5th retry attempt onwards.

| Retry attempt | Delay    |
| ------------- | -------- |
| 1st           | 12s      |
| 2nd           | 2m 28s   |
| 3rd           | 30m 8s   |
| 4th           | 6h 7m 6s |
| 5th           | 24h      |
| 6th           | 24h      |
| ...           | ...      |

### Temporary Disablement

If a webhook endpoint consistently fails, it will be automatically disabled after a series of failed attempts.

Notifications will be sent to the Workspace owners at the following intervals:

* After 5, 10, and 15 consecutive failed attempts.
* On the 20th consecutive failed attempt, the **webhook will be disabled**.

This mechanism ensures that non-responsive endpoints do not continue to receive retry attempts indefinitely, maintaining system efficiency and preventing unnecessary load on both the sender and receiver.

You can re-enable a disabled webhook by clicking on the **Enable webhook** button in the webhook details page.

## IP allowlist

If your endpoint sits behind a firewall or IP allowlist, you'll need to allow traffic from the following IP addresses – these are the IPs that Dub uses to deliver events (and retries):

```
3.125.136.186/32
18.198.95.233/32
3.69.157.89/32
3.69.220.58/32
52.58.170.183/32
3.74.98.166/32
100.51.235.36/32
3.230.190.166/32
3.232.167.47/32
34.198.105.68/32
34.199.12.224/32
18.206.116.131/32
```

<Note>
  This list can potentially change over time. For the most up-to-date list, fetch it directly from [api.upstash.com/v2/qstash/ipv4](https://api.upstash.com/v2/qstash/ipv4).
</Note>


# Verify webhook requests
Source: https://dub.co/docs/webhooks/verify-webhook-requests

Learn how to verify webhook requests to ensure they're coming from Dub.

With signature verification, you can determine if the webhook came from Dub, and has not been tampered with in transit.

All webhooks are delivered with a `Dub-Signature` header. Dub generates this header using a secret key that only you and Dub know.

An example header looks like this:

```
Dub-Signature: c9ed6a2abf93f59d761eea69908d8de00f4437b5b6d7cd8b9bf5719cbe61bf46
```

## Finding your webhook's signing secret

You can find your webhook's signing secret in the **Update Details** tab:

<Frame>
  <img alt="Webhook signing secret" />
</Frame>

Make sure to keep this secret safe by only storing it in a secure environment variable (e.g. `DUB_WEBHOOK_SECRET`). Do not commit it to git or add it in any client-side code.

## Verifying a webhook request

To verify, you can use the secret key to generate your own signature for each webhook. If both signatures match then you can be sure that a received event came from Dub.

The steps required are:

1. Get the raw body of the request.
2. Extract the signature from the `Dub-Signature` header.
3. Calculate the HMAC of the raw body using the `SHA-256` hash function and the secret.
4. Compare the calculated `HMAC` with the one sent in the `Dub-Signature` header. If they match, the webhook is verified.

Here's an example of how you can verify a webhook request in different languages:

<CodeGroup>
  ```javascript Next.js theme={null}
  export const POST = async (req: Request) => {
    const webhookSignature = req.headers.get("Dub-Signature");
    if (!webhookSignature) {
      return new Response("No signature provided.", { status: 401 });
    }

    // Copy this from the webhook details page
    const secret = process.env.DUB_WEBHOOK_SECRET;
    if (!secret) {
      return new Response("No secret provided.", { status: 401 });
    }

    // Make sure to get the raw body from the request
    const rawBody = await req.text();

    const computedSignature = crypto
      .createHmac("sha256", secret)
      .update(rawBody)
      .digest("hex");

    if (webhookSignature !== computedSignature) {
      return new Response("Invalid signature", { status: 400 });
    }

    // Handle the webhook event
    // ...
  };
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def webhook():
      # Get the signature from the header
      webhook_signature = request.headers.get('Dub-Signature')
      if not webhook_signature:
          abort(401, 'No signature provided.')

      # Copy this from the webhook details page
      secret = os.environ.get('DUB_WEBHOOK_SECRET')
      if not secret:
          abort(401, 'No secret provided.')

      # Get the raw body of the request
      raw_body = request.data

      # Calculate the HMAC
      computed_signature = hmac.new(
          secret.encode('utf-8'),
          raw_body,
          hashlib.sha256
      ).hexdigest()

      if webhook_signature != computed_signature:
          abort(400, 'Invalid signature')

      # Handle the webhook event
      # ...

      return 'OK', 200
  ```

  ```go Go theme={null}

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  	"io/ioutil"
  	"net/http"
  	"os"
  )

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
  	// Get the signature from the header
  	webhookSignature := r.Header.Get("Dub-Signature")
  	if webhookSignature == "" {
  		http.Error(w, "No signature provided.", http.StatusUnauthorized)
  		return
  	}

  	// Copy this from the webhook details page
  	secret := os.Getenv("DUB_WEBHOOK_SECRET")
  	if secret == "" {
  		http.Error(w, "No secret provided.", http.StatusUnauthorized)
  		return
  	}

  	// Read the raw body
  	body, err := ioutil.ReadAll(r.Body)
  	if err != nil {
  		http.Error(w, "Error reading request body", http.StatusInternalServerError)
  		return
  	}

  	// Calculate the HMAC
  	h := hmac.New(sha256.New, []byte(secret))
  	h.Write(body)
  	computedSignature := hex.EncodeToString(h.Sum(nil))

  	if webhookSignature != computedSignature {
  		http.Error(w, "Invalid signature", http.StatusBadRequest)
  		return
  	}

  	// Handle the webhook event
  	// ...

  	w.WriteHeader(http.StatusOK)
  	w.Write([]byte("OK"))
  }
  ```
</CodeGroup>

## Why is signature verification important?

Signature verification is a crucial security measure that protects against request forgery and data tampering. Without verification, malicious actors could send fake webhook events to your endpoint, potentially triggering unauthorized actions.

The HMAC-SHA256 signature verification process ensures that only Dub can generate valid webhook requests and that payloads haven't been modified in transit. This provides both authentication (confirming the sender is Dub) and integrity (ensuring the message hasn't been tampered with).


# Dub Help Center
Source: https://dub.co/help

Find answers to all your Dub-related questions.

## Categories

<CardGroup>
  <Card title="Managing a program" icon="users" href="/help/article/dub-partners">
    Run affiliate/referral/influencer programs, with 1-click global payouts
  </Card>

  <Card title="Partnering with programs" icon="money-bill" href="/help/article/partner-profile">
    Join programs, manage your profile, get paid for referrals
  </Card>

  <Card title="Managing your links" icon="link" href="/help/article/dub-links">
    Create links, track analytics, manage custom domains
  </Card>

  <Card title="Managing your workspace" icon="building" href="/help/article/what-is-a-workspace">
    Manage your workspace, invite teammates, configure SAML/SSO
  </Card>
</CardGroup>

## Popular Articles

<CardGroup>
  <Card title="Dub Partners Overview" href="/help/article/dub-partners" />

  <Card title="Setting up your program" href="/help/article/setting-up-your-program" />

  <Card title="Sending partner payouts" href="/help/article/partner-payouts" />

  <Card title="Receiving partner payouts" href="/help/article/receiving-payouts" />
</CardGroup>


# How to run A/B tests with Dub Links?
Source: https://dub.co/help/article/ab-testing

Run A/B tests to compare different short-link variations and identify what drives the highest conversions.

<Info>
  A/B testing is only available on Business plans and higher. [View all pricing
  plans](https://dub.co/pricing).
</Info>

## What is A/B testing?

A/B testing is a method of comparing two or more variations of a destination URL to see which performs better. It works by randomly redirecting users who click the same short link to different target URLs, then measuring which version gets better results based on metrics.

<Note>
  If you combine A/B testing with [device targeting](/help/article/device-targeting) or [geo targeting](/help/article/geo-targeting) on the same link, the targeting rules are evaluated in this order:

  1. **Device targeting** (iOS/Android) – checked first
  2. **Geo targeting** – checked next
  3. **A/B testing** – applied last

  A/B testing only applies to traffic that isn't already redirected by a device or geo targeting rule.
</Note>

## Where to find A/B testing?

When the link builder is open, you'll find the **A/B test** button in the bottom action row. Click to open the dialogue.

<Frame>
  <img alt="A/B test location" />
</Frame>

## A/B testing builder

With the builder open, you are able to add additional destination URLs, set the traffic percentage, and set the completion date. A minimum of 2 URLs is required to create the test.

<Frame>
  <img alt="A/B test builder" />
</Frame>

### Testing URLs

You can enter a maximum of 4 additional testing URLs that you'd like to send your traffic to.

<Frame>
  <img alt="Testing URLs" />
</Frame>

If you need to remove any URLs you've added, click the **`X`** icon to the right of each input to remove it.

### Traffic split

By default, the traffic is split evenly between each of the URLs, but this can be changed depending on your preference.

<Frame>
  <img alt="Traffic split" />
</Frame>

Click and drag the dividers between each group to adjust the traffic to each
destination URL. The minimum percentage you can set traffic to is 10%. ###
Completion Date By default, A/B tests will be set to run for 2 weeks, as that is
enough time to collect performance data to make a decision

<Frame>
  <img alt="Completion date" />
</Frame>

You can run a test for a shorter period, but that's not recommended because in
most cases, you won't collect enough data to make a decision. The longest you
can run a test for is 6 weeks.

## Active A/B tests

While an A/B test is active, a `flask` icon is shown on each link. You can see this while viewing all your links.

<Frame>
  <img alt="Links view" />
</Frame>

You can click this icon to expand the current link view to show all the
destination URLs you've entered for testing. This will show you the traffic
percentage you've set and the click, lead, and sale performance.

<Frame>
  <img alt="Links view expanded" />
</Frame>

## Completing A/B tests

There are two ways A/B tests can be completed. They can either be manually ended early or the scheduled completion date passes.

<Steps>
  <Step title="Completion date has passed">
    By default, your A/B test will complete when the scheduled date passes. Here's how the new destination URL is selected:

    1. Select the link with the highest amount of conversions
    2. If conversions are tied, select the link with the highest conversion rate (conversions ÷ clicks)
    3. If still tied, select the link with the highest clicks

    `OR - If no conversions are recorded or tracked`

    1. Select the link with the highest number of leads
    2. If leads are tied, select the link with the highest lead rate (leads ÷ clicks)
    3. If still tied, select the link with the highest clicks
  </Step>

  <Step title="Ending the test manually">
    To end your test before the scheduled date, open your A/B settings and click **End A/B test** in the bottom left of the modal.

    <Frame>
      <img alt="End test early" />
    </Frame>

    From here, you'll select the new destination URL for your short link.

    <Frame>
      <img alt="Select the destination URL" />
    </Frame>

    After clicking **End test**, make sure to save your link changes in the next
    screen to confirm the changes.
  </Step>
</Steps>


# Application analytics
Source: https://dub.co/help/article/application-analytics

Use application analytics to understand where partner applications come from and how applicants move from visit to approval.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Application analytics helps you understand how people discover your partner program and where applicants drop off before becoming approved partners.

Use it to compare application sources, measure conversion through each step of the application funnel, and see which channels are bringing in applicants who are most likely to be approved.

## Where to find application analytics?

When viewing your Partner Program navigation, click on the **Analytics** page in the **Insights** group.

In the analytics chart, select **Applications** from the top data selector.

<Frame>
  <img alt="Application analytics dashboard" />
</Frame>

## Application analytics views

The application analytics dashboard uses filtering and date range controls like the [program analytics](/help/article/program-analytics), but focuses the chart on application acquisition and approval flow.

### 1. Application chart

The application chart shows application activity across the selected date range. You can use it to identify spikes, dips, and trends in how potential partners move through the application process.

| Step        | Description                                      |
| ----------- | ------------------------------------------------ |
| Visits      | People who visited your program application page |
| Starts      | Visitors who started the application form        |
| Submissions | Applicants who submitted the application form    |
| Approvals   | Submitted applications that were approved        |

<Frame>
  <img alt="Application analytics time-series chart" />
</Frame>

**Chart toggle**

You can change the chart type displayed by clicking the `time-chart | application funnel` toggle in the top right of the chart area.

<Frame>
  <img alt="Application analytics chart toggle" />
</Frame>

When the chart type is switched to **application funnel**, you can view the percentage of visitors that complete each stage of the application process.

**Application funnel**

The application funnel shows how potential partners progress through each step of the application process. Each step shows the total count for the selected date range and the conversion rate from the first visit.

<Frame>
  <img alt="Application analytics funnel chart" />
</Frame>

This helps you understand whether applicants are dropping off before starting, before submitting, or during review.

### 2. Aggregated data for different facets (top views)

These are more commonly known as the "Top Views" in Dub Analytics. These views show your top application **Sources** and **Countries**, and allow you to explore your program data further.

Use **Application source** to identify which sources are driving application interest, such as the Dub Program Marketplace, direct applications, referrers, and other websites that send traffic to your program.

Use **Application country** to compare application demand across countries, understand where your program is attracting partners, and decide whether your application settings or partner groups should be tailored by region.

<Frame>
  <img alt="Aggregated application analytics data" />
</Frame>

## Reviewing applications

To review the applicants behind your application analytics, click **View Applications** in the top right of the analytics page.

<Frame>
  <img alt="View applications button" />
</Frame>

You can also go directly to the [Applications page](/help/article/program-applications) to approve, reject, or review pending applications.

## Filtering application analytics

You can click the **Filter** button in the top left to narrow application analytics by partner, application source, and country.

<Frame>
  <img alt="Filtering application analytics" />
</Frame>

| Filter  | Description                                        |
| ------- | -------------------------------------------------- |
| Partner | Select a specific program partner                  |
| Source  | Select a specific application source               |
| Country | Select a specific country where applications start |

The date picker controls the reporting period for the application funnel and source tables.


# Archiving domains on Dub
Source: https://dub.co/help/article/archiving-domains

Learn more about how you can archive domains on Dub to better manage your domains.

You can archive your custom domains on Dub to better manage your domains.

When you archive a domain:

* The domain will not show up in the [link builder](/help/article/dub-link-builder)
* The domain will also be hidden from the **Active** domain list
* Their links will continue to work and will still show up on the links dashboard

<Frame>
  <img alt="Archived domains will only show up under the 'Archived' tab under your workspace domains" />
</Frame>

To archive a domain, you can follow the steps below:

1. Go to your workspace settings and click on the **Domains** tab.
2. Click on the `⋮` button next to the domain you want to archive, and select **Archive**.
3. In the modal that appears, select **Confirm archive** to archive the domain.

The domain will now be archived and will no longer show up in the active domain list.


# Configuring SAML SSO with Azure AD
Source: https://dub.co/help/article/azure-saml

For Dub Enterprise users, you can securely manage your team's access to Dub using Azure AD SAML SSO.

<Tip>
  This feature is only available on [Dub Enterprise](https://dub.co/enterprise).
</Tip>

For Dub Enterprise users, you can securely manage your team's access to Dub using [Azure AD SAML SSO](https://learn.microsoft.com/en-us/azure/active-directory/architecture/auth-saml).

## Step 1: Create or Select SAML Application

In your [Azure Admin console](https://portal.azure.com/), select **Azure Active Directory** (or search for it in the search bar).

<Frame>
  <img alt="Azure Active Directory option on the Azure Dashboard" />
</Frame>

Then, click on **Enterprise applications** from the left sidebar.

<Frame>
  <img alt="Enterprise applications option on the Azure Dashboard" />
</Frame>

If you already have an existing Azure AD SAML application, select it from the list and move on to [Step 2](#step-2-configure-saml-applicaation).

If not, click on **New application** at the top.

<Frame>
  <img alt="Create new application button on the Azure Dashboard" />
</Frame>

In the next screen, click on **Create your own application**. Give your application a **Name** (e.g. "Dub") and click **Create**.

<Frame>
  <img alt="Create your own application option on the Azure Dashboard" />
</Frame>

## Step 2: Configure SAML Application

Under the **Manage** section in the left sidebar, select **Single sign-on**. Then, click on **SAML**.

<Frame>
  <img alt="SAML option on the Azure Dashboard" />
</Frame>

Under the **Basic SAML Configuration** section, click on **Edit**.

<Frame>
  <img alt="Edit button on the SAML Settings page on the Azure Dashboard" />
</Frame>

This will open up a sheet overlay. Under **Basic SAML Configuration**, enter the following values:

```text title="Identifier (Entity ID)" theme={null}
https://saml.dub.co
```

```text title="Reply URL (Assertion Consumer Service URL)" theme={null}
https://api.dub.co/auth/saml/callback
```

<Frame>
  <img alt="Basic SAML Configuration section on the Azure Dashboard" />
</Frame>

Click **Save** in the menu bar to save your changes.

<Frame>
  <img alt="Basic SAML Configuration section on the Azure Dashboard" />
</Frame>

## Step 3: Attribute Mapping

Click **Edit** on the **Attributes & Claims** section.

<Frame>
  <img alt="Attributes & Claims section on the Azure Dashboard" />
</Frame>

Under **Additional claims**, make sure the following entries are present:

| Name                                                                                                                                     | Value                  |
| ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress) | user.mail              |
| [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname)       | user.givenname         |
| [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name)                 | user.userprincipalname |
| [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname)           | user.surname           |

<Frame>
  <img alt="Additional claims section on the Azure Dashboard" />
</Frame>

Once that's done, click on the `X` button in the top right corner to go back to the main settings page (or click the back button in your browser).

## Step 4: Copy the Metadata URL

Scroll down to the 3rd section on the page, **SAML Certificates**. Copy the **App Federation Metadata Url** value and return to the Dub dashboard.

<Frame>
  <img alt="Metadata URL on the Azure Dashboard" />
</Frame>

## Step 5: Configure SAML SSO on Dub

In your workspace settings, click on **Security** in the **Workspace** group.

<Frame>
  <img alt="Directory Sync section on the Dub Dashboard" />
</Frame>

Under the **SAML Single Sign-On** section, click on **Configure**. This will open up the SAML SSO modal:

1. Select **Azure AD** as the SAML provider.
2. Enter the **App Federation Metadata Url** value that you copied from Step 4.
3. Click **Save changes**.

<Frame>
  <img alt="SAML SSO Modal" />
</Frame>

## Step 6: Assign Users

<Warning>
  We highly recommend configuring [SCIM Directory
  Sync](/help/article/azure-scim) before assigning users & groups to your
  workspace. This will ensure that your users are automatically added to your
  workspace when they sign in for the first time, as well as automatically
  removed when they are deactivated in Azure.
</Warning>

Once you've configured SAML SSO, you can start assigning users & groups to your workspace.

From your application, click the **Users and groups** from the left navigation menu and click **Add user/group**.

<Frame>
  <img alt="Adding users in Azure AD" />
</Frame>

Click on **None Selected** under **Users**.

From the right side of the screen, select the users you want to assign to the app and click the **Select** button. Thenm click **Assign** to those users to your app.

<Frame>
  <img alt="Assigning users in Azure AD" />
</Frame>

Your assigned users should now receive an invitation email to join your Dub workspace.

<Frame>
  <img alt="SAML invite email" />
</Frame>

<Warning>
  Azure AD SCIM provisioning can take [anywhere between 20-40 minutes to
  sync](https://learn.microsoft.com/en-us/azure/databricks/administration-guide/users-groups/scim/aad#provisioning-tips).
  This means that it may take up to 40 minutes for your users to receive the
  invitation email and be able to join your Dub workspace.
</Warning>

They will also be able to sign in to Dub using Azure AD SSO.


# Configuring SCIM Directory Sync with Azure AD
Source: https://dub.co/help/article/azure-scim

For Dub Enterprise users, you can automatically provision and deprovision users from Azure AD to Dub using SCIM Directory Sync.

<Tip>
  This feature is only available on [Dub Enterprise](https://dub.co/enterprise).
</Tip>

For Dub Enterprise users, you can automatically provision and deprovision users from your Azure Active Directory (AD) to Dub using [SCIM Directory Sync](https://learn.microsoft.com/en-us/azure/active-directory/architecture/sync-scim).

## Step 1: Configure Directory Sync on Dub

In your workspace dashboard on Dub, click on the **Settings** tab in the menu bar at the top. Then, click on the **Security** tab in the sidebar.

<Frame>
  <img alt="Directory Sync section on the Dub Dashboard" />
</Frame>

Under the **Directory Sync** section, click on **Configure**. This will open up the Directory Sync modal:

1. Select **Azure AD** as the Directory Provider.
2. Click **Save changes**.

<Frame>
  <img alt="SCIM Modal" />
</Frame>

This will generate a Directory Sync connection for your Dub workspace, and return 2 values, which will be needed in Step 2:

1. **Tenant URL**
2. **Secret Token**

<Frame>
  <img alt="SCIM Modal" />
</Frame>

## Step 2: Add Provisioning to SAML Application

Click on the **Provisioning** tab of your existing Dub Okta SAML application that you want to enable SCIM provisioning for.

<Frame>
  <img alt="Provisioning tab of Okta SAML application" />
</Frame>

In the Provisioning tab, click on **Get started**.

<Frame>
  <img alt="Get started with provisioning" />
</Frame>

Select **Automatic** from the **Provisioning Mode** dropdown. Under the **Admin Credentials** section, enter the values that you obtained from Step 1:

1. **Tenant URL**
2. **Secret Token**

<Frame>
  <img alt="Provisioning tab of Azure SAML application" />
</Frame>

Click on **Test Connection** to test the connection to see if the credentials are correct, then click **Save** to save the credentials.

Expand the **Mappings** section and ensure group and user attribute mappings are enabled for your app. The default mapping should work.

<Frame>
  <img alt="Mappings section of Azure SAML application" />
</Frame>

Expand the **Settings** section and make the following changes:

* Select **Sync only assigned users and groups** from the **Scope** dropdown.
* Confirm the **Provisioning Status** is set to **On**.

Click **Save** to save the changes.

<Frame>
  <img alt="Settings section of Azure SAML application" />
</Frame>

Congratulations! You've successfully configured SCIM provisioning for your Dub workspace.

## Step 3: Assign Users

Once you've configured Directory sync, you can assign users to Dub directly within Azure AD.

From your application, click the **Users and groups** from the left navigation menu and click **Add user/group**.

<Frame>
  <img alt="Adding users in Azure AD" />
</Frame>

Click on **None Selected** under **Users**.

From the right side of the screen, select the users you want to assign to the app and click the **Select** button. Thenm click **Assign** to those users to your app.

<Frame>
  <img alt="Assigning users in Azure AD" />
</Frame>

Your assigned users should now receive an invitation email to join your Dub workspace.

<Frame>
  <img alt="SAML invite email" />
</Frame>

<Warning>
  Azure AD SCIM provisioning can take [anywhere between 20-40 minutes to
  sync](https://learn.microsoft.com/en-us/azure/databricks/administration-guide/users-groups/scim/aad#provisioning-tips).
  This means that it may take up to 40 minutes for your users to receive the
  invitation email and be able to join your Dub workspace.
</Warning>

They will also be able to sign in to Dub using Azure AD SSO


# How to manage your links in bulk
Source: https://dub.co/help/article/bulk-link-actions

Learn how to speed up your link management workflow with bulk link actions.

At Dub, we understand that "time is money".

Imagine you've created a dozen links for a marketing campaign, but you realized that you forgot to assign them to a tag when creating them.

Instead of [manually tagging them in the link builder](/help/article/how-to-use-tags), what if you could *tag all of them at once*?

With bulk link actions, you can do exactly that.

## Bulk selecting links

To select a link, hover over the logo of the link and click on it. A check mark will show up in lieu of the link logo, and a selection toolbar will appear at the bottom of your screen:

<Frame>
  <img alt="" />
</Frame>

You can also select multiple links at once with `SHIFT` + `CLICK`:

<Frame>
  <video />
</Frame>

## Updating links in bulk

Then, once you've selected your links, you can use the toolbar to [perform bulk actions](#supported-bulk-link-actions) on the selected links:

<Frame>
  <img alt="" />
</Frame>

<Tip>Hitting `ESC` deselect all currently selected links.</Tip>

## Supported bulk link actions

Here's a list of all supported bulk link actions, along with their respective keyboard shortcuts:

| Action                                                                                     | Shortcut |
| ------------------------------------------------------------------------------------------ | -------- |
| [Tagging](/help/article/how-to-use-tags) multiple links                                    | `T`      |
| Moving multiple links to the same folder                                                   | `M`      |
| Enabling/disabling [conversion tracking](/help/article/dub-conversions) for multiple links | `C`      |
| Archiving multiple links                                                                   | `A`      |
| Deleting multiple links                                                                    | `X`      |

Are there any other bulk link actions you'd like to see? [Let us know](https://dub.co/contact/support)!

## Programmatically managing links in bulk

You can also manage links in bulk using the [Dub API](/docs/api-reference/introduction).

Here are a few things you can do with the API:

* [Create links in bulk](/docs/api-reference/endpoint/bulk-create-links)
* [Update links in bulk](/docs/api-reference/endpoint/bulk-update-links)
* [Delete links in bulk](/docs/api-reference/endpoint/bulk-delete-links)


# How to choose a custom domain for your short links
Source: https://dub.co/help/article/choosing-a-custom-domain

Learn how to choose the right custom domain for your short links on Dub.

Custom domains are a great way to [improve the click-through rates of your short links](https://dub.co/blog/custom-domains). They are especially useful if you want to use a domain that is relevant to your audience and content.

On Dub, you can add custom domains for free. The number of domains that you can add depends on your plan:

* **Free plan**: 3 domains
* [**Pro plan**](https://dub.co/pricing): 10 domains
* [**Business plan**](https://dub.co/pricing): 40 domains
* **Enterprise plan**: Unlimited domains

## Which custom domain should I choose for my short link?

When choosing a custom domain for your short links, consider the following factors:

1. **Brand**: Choose a domain that best reflects your brand – ideally containing your brand name in it. For example, if your company domain is `jupiter.com`, you can use a domain like `jupiter.link` or `go.jupiter.com`.
2. **Length**: Shorter domains are easier to remember and type. To achieve this, some companies might opt for acronyms or abbreviations. For example, one of our customers [Tinybird](https://dub.co/customers/tinybird) uses `tbrd.co` for their short domain.

<Tip>
  The only exception here is you're going for a `.link` TLD, or a `go.`
  subdomain, which are both slightly longer than your original domain.
</Tip>

Here are some examples of custom domains that you can use for your short links – based on what we've seen [our customers](https://dub.co/customers) use:

| Option         | Format          | Real-world examples                                                                                                                | Usage on Dub |
| -------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Subdomains     | `go.domain.com` | **Clerk** → `go.clerk.com`<br />**Nuxt** → `go.nuxt.com`<br />**Cal.com** → `go.cal.com`<br />**Causal** → `go.causal.app`         | 32.1%        |
| Different TLDs | `domain.link`   | **Framer** → `framer.link`<br />**Supabase** → `supabase.link`<br />**Vercel** → `vercel.fyi`<br />**Replicate** → `replicate.fyi` | 6.6%         |
| Domain hacks   | `doma.in`       | **Basedash** → `baseda.sh`<br />**Inngest** → `innge.st`<br />**Lugg** → `lu.gg`<br />**Crowdin** → `crowd.in`                     | 1.9%         |

### Option 1: Use a subdomain

This is the easiest option to get started with – especially since you don't need to buy a new domain. Plus, using a subdomain is also better for ownership verification since users can be certain that the domain is owned by your entity (as opposed to a separate domain like `company.tld`).

For instance, if you're using `domain.com` for your main website, you can [use a subdomain](/help/article/how-to-add-custom-domain#step-2b-adding-a-subdomain) like `go.domain.com` for your short links.

Some real-world examples of this are:

* **Clerk** → `go.clerk.com`
* **Nuxt** → `go.nuxt.com`
* **Cal.com** → `go.cal.com`
* **Causal** → `go.causal.app`

<Note>
  At the time of writing, 1,293 out of 4,024 verified domains on Dub use a
  subdomain for their short links – a 31% usage rate.
</Note>

### Option 2: Use a different TLD

Alternatively, you can use a different top-level domain (TLD) like `.link` or `.fyi` for your short links. With this option, you get to use a similar length domain – if not shorter – for your short links, with the trade-off being that you'll need to scoop out a few extra dollars for the additional domain.

For example, if you're using `domain.com` for your main website, you can use a TLD like `domain.link` for your short links.

Some real-world examples of this are:

* **Framer** → `framer.link`
* **Supabase** → `supabase.link`
* **Vercel** → `vercel.fyi`
* **Replicate** → `replicate.fyi`

<Note>
  At the time of writing, 266 out of 4,024 verified domains on Dub use a
  different TLD for their short links – a 6.6% usage rate.
</Note>

<Tip>
  If you're on a [Pro plan](https://dub.co/pricing) or higher, you can also claim a [complimentary .link custom domain](/help/article/free-dot-link-domain) and use it as your short link domain.

  <Frame>
    <img alt="Free .link domain" />
  </Frame>
</Tip>

### Option 3: Use a domain hack

Last but not least, you can get creative with your short link domain by using a domain hack. This is a technique where you split your company name into two parts and use the first part as the domain and the second part as the top-level domain (TLD).

For example, if you're using `domain.com` for your main website, you can use a domain hack like `doma.in` for your short links.

Some real-world examples of this are:

* **Basedash** → `baseda.sh`
* **Inngest** → `innge.st`
* **Lugg** → `lu.gg`
* **Crowdin** → `crowd.in`

<Note>
  At the time of writing, 78 out of 4,024 verified domains on Dub use a domain
  hack for their short links – a 1.9% usage rate.
</Note>


# Commission analytics
Source: https://dub.co/help/article/commission-analytics

Use commission analytics to understand how much your partners are earning over time and which commissions are driving your program payouts.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Commission analytics helps you analyze your partner program's commission earnings over time. It is useful when you want to understand payout trends, compare commission activity across a date range, or see which partners and referral activity are driving earnings.

## Where to find commission analytics?

When viewing your Partner Program navigation, click on the **Analytics** page in the **Insights** group.

In the analytics chart, select **Commissions** from the top data selector.

<Frame>
  <img alt="Program analytics location" />
</Frame>

## Commission analytics views

The commission analytics dashboard uses filtering and date range controls like the [program analytics](/help/article/program-analytics), but focuses the chart on commission earnings.

### 1. Commission chart

The commission chart shows commission earnings across the selected date range. You can use it to identify spikes, dips, and trends in partner payouts.

<Frame>
  <img alt="Time-series chart" />
</Frame>

**Data toggle**

You can change the data displayed by clicking the `$ | 123` toggle in the top right of the data type section.

<Frame>
  <img alt="Time-series chart" />
</Frame>

By default, `$` is selected to show commission value. Switching to `123` changes the chart to show the number of commissions instead.

### 2. Partner commission data

Below the chart, you can review partner-level commission performance for the selected period. This helps you identify which partners are generating the most commission activity.

<Frame>
  <img alt="Partner data table" />
</Frame>

To inspect individual commission records, go to the [Commissions page](/help/article/partner-commissions).

### 3. Aggregated data for different facets (top views)

These are more commonly known as the "Top Views" in Dub Analytics. These views show your top **Partner Groups** and commission **Types**, and allow you to explore your program data further.

<Frame>
  <img alt="Aggregated data for different facets" />
</Frame>

## Filtering commission analytics

You can click the **Filter** button in the top left to narrow commission analytics by partner, group, and commission type.

The date picker controls the reporting period for the commission chart and partner data.

<Frame>
  <img alt="Program analytics location" />
</Frame>

| Filter          | Description                                                     |
| --------------- | --------------------------------------------------------------- |
| Partner         | Select a specific program partner                               |
| Partner Group   | Select a specific [partner group](/help/article/partner-groups) |
| Partner Tag     | Select a specific [partner tag](/help/article/partner-tags)     |
| Commission type | Select a specific commission type (eg: click, lead, sale)       |


# How commissions and payouts work on Dub
Source: https://dub.co/help/article/commissions-payouts

Learn about the different commission and payout statuses on Dub and when you can expect to be paid by the programs you work with.

As an affiliate partner on Dub, you can participate in various affiliate programs and receive commission payouts directly into your bank account.

In this article, we'll explore how commissions and payouts work on Dub, their various statuses, and when you can expect to be paid by the programs you work with.

## Commission statuses

When you receive a commission on Dub, it goes through the following statuses:

* `Pending`: This is the initial status for a commission. Depending on your program's [holding period](#what-does-holding-period-mean), commissions can stay in "pending" status anywhere from less than a day to 90 days.
* `Processed`: When a commission has been added to a payout (after any applicable [holding period](#what-does-holding-period-mean)), its status will change from "pending" to "processed".
* `Paid`: When the payout containing the commission has been paid by the program, the commission will be updated to "paid" status.

Occasionally, commissions can also be updated to the following statuses:

* `Refunded`: If the sale for the commission was refunded. This is usually automatically detected via our [Stripe integration](https://dub.co/integrations/stripe), or manually by the program.
* `Fraud`: If a sale was marked as fraudulent by your program.
* `Canceled`: If a sale was marked as canceled/voided by your program (e.g. in cases of self-referrals where commissions are not eligible for payout).
* `Duplicate`: If a sale was marked as duplicate by your program.

In these cases, we recommend reaching out to your program's support email with any questions/appeals that you might have.

## Payout statuses

Payouts on Dub go through the following statuses:

* `Pending`: When you start earning commissions from a program, they'll be accrued under a payout entry. If the program has a [minimum payout amount](#what-does-minimum-payout-amount-mean) set, your payout amount will need to reach the threshold to become eligible for payment.
* `Processing`: When a program initiates a payout to you, the payout will be updated to a "processing" state until the payment settles on Dub. This process can take up to 5 business days.
* `On Hold`: When a commission is temporarily paused due to pending risk events. On-hold commissions cannot be paid out until the program reviews and resolves the issue. If you believe this is incorrect, reach out to the program’s support team.
* `Processed`: Once the program's payout has settled on Dub, the payout will be updated to a "processed" state. If the payout amount is above your [minimum withdrawal balance](/help/article/receiving-payouts#what-is-the-minimum-withdrawal-amount-and-how-does-it-work), the funds will be automatically paid out to your connected bank account.
* `Sent`: When your payout is on its way to your connected bank account, it will be updated to a "sent" state. Depending on your bank location, this can take anywhere from 1 to 14 business days (you will see the estimated arrival date in the notification email from Dub).
* `Completed`: When the payout is completed and the funds are deposited into your bank account.

If you haven't already, [set up your bank account to receive payouts on Dub](/help/article/receiving-payouts).

<Frame>
  <img alt="Payout statuses" />
</Frame>

## Frequently asked questions

### What does "holding period" mean?

Programs can set certain **holding periods** for earned commissions, during which your commissions will be kept in `pending` status and won't be eligible for payout:

* 0 days
* 14 days
* 30 days (most common)
* 60 days
* 90 days

This is usually done for security / fraud prevention purposes, as well as to account for potential refunds/chargebacks from referred customers.

Note that the holding period only applies to commissions from `click`, `lead`, and `sale` events. Commissions from [bounties](/help/article/program-bounties) or one-time custom commissions from the program are **not** subject to the holding period, and become eligible for payout at the next payout cycle.

### What does "minimum payout amount" mean?

Programs can also set a **minimum payout amount** (e.g. \$100), where commissions will be accrued and only eligible for payout after it surpasses the minimum threshold.

The default payout amount on Dub is \$0.

### When will I get paid?

This depends on your program's payment schedule, which is usually at the beginning or the end of the month. We recommend reaching out to your program's support team to confirm the exact payment date.


# Communicating with your programs
Source: https://dub.co/help/article/communicating-with-programs

Learn about using the messaging center to stay connected with your programs, view conversations, and send messages.

On Dub, you can use the built-in Messaging Center to communicate directly with the programs you're enrolled in.

This is helpful for discussing any questions about your rewards structure, [commissions](/help/article/commissions-payouts), and [partner payouts](/help/article/receiving-payouts).

## Where can I find my messages?

In your main navigation, click the **Messages** icon to see your inbox and compose new messages.

<Frame>
  <img alt="Messages location" />
</Frame>

## Messages overview

The messaging center has three panels:

<Frame>
  <img alt="Message panels" />
</Frame>

| Panel        | Content                                         |
| ------------ | ----------------------------------------------- |
| Inbox        | All your conversations in one place.            |
| Conversation | The full thread with your selected program.     |
| Details      | Key info and performance stats for the program. |

### Inbox panel

Your Inbox lists every conversation with the programs you're enrolled in. Select one to pick up where you left off, or click the **Pencil** button in the top right to compose something new.

<Frame>
  <img alt="Inbox panel" />
</Frame>

Each conversation row shows:

* **Unread indicator** – shows if you’ve read the message
* **Program** – program's name and program image
* **Last activity** – most recent message from you or the program
* **Excerpt** – short preview of the latest message

<Frame>
  <img alt="Inbox pieces" />
</Frame>

### Conversation panel

In the Conversation panel, you’ll see the full thread with your partner. Workspace members can reply too, with their profile images shown beside each message.

<Frame>
  <img alt="Conversation panel" />
</Frame>

**Message status**

Each message sent has a check mark next to the send time:

* **Single check mark** – delivered
* **Double check marks** – delivered and read (either in email or in the app)

### Details panel

The details panel is collapsible and shows the program's information, your performance stats, referral link, and eligible rewards/bounties.

<Frame>
  <img alt="Details panel" />
</Frame>

## Messaging programs

You can message any program you are enrolled in as long as they have enabled the Messaging Center. If messaging is not enabled, the program’s support email address will appear instead:

<Frame>
  <img alt="Details panel" />
</Frame>


# How to set custom link previews for your Dub links?
Source: https://dub.co/help/article/custom-link-previews

Learn how to customize how your links show up on social media to improve click-through rates.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

Dub is the only link management platform that lets you customize the social media cards for your links, so you can have full control over how your links look when shared on social media and improve click-through rates.

## Why set custom link previews?

When running marketing campaigns, it's important to have a consistent brand voice across your links. However, that isn't always the case, especially when collaborating with external partners.

For example, your company was recently featured on TechCrunch, but the default link preview is a generic Unsplash image that doesn't accurately reflect your brand.

With Dub, you can set a custom-designed social media card for your link, which **greatly improves the click-through rate and performance of your short link**.

## How to set a custom link preview?

To leverage this feature, toggle the "Custom Link Previews" switch in the [link builder](/help/article/dub-link-builder). This will reveal the following fields:

1. **Image**: The image that will be used for the social media card. You can upload an image from your computer or drag and drop an image into the field (recommended size: 1200 x 630 pixels, max 1.5MB).
2. **Title**: The title that will be used for the social media card (max 120 characters).
3. **Description**: The description that will be used for the social media card (max 240 characters).

<Frame>
  <img alt="Create custom link previews for your links" />
</Frame>

You can also quickly access the **Link Preview** feature by using the keyboard shortcut `L` while in the link builder.

## Generating custom link previews using AI

You can also generate custom link previews for your links using AI simply by clicking on the `✨` button:

<Frame>
  <video />
</Frame>

Behind the scenes, [Dub AI](https://dub.co/blog/introducing-dub-ai) leverages the latest Large Language Models (LLMs) to analyze the content of the link and generate a title and description that is SEO-friendly and increases the click-through rate.

## How do custom link previews look like?

When you share a Dub link that has a custom social media card, they will show up with the title, description, and image that you set on all social media platforms.

For example, this is a link that Taimur from Causal [shared on Twitter](https://x.com/taimurabdaal/status/1762516755153387886) when they launched their YC data dashboard project:

<Frame>
  <img alt="Causal's YC dashboard tweet" />
</Frame>

By leveraging Dub's custom link previews feature, they were able to set a custom title and preview image for their link, [go.causal.app/ycombinator](https://go.causal.app/ycombinator).

This resulted in a more engaging and informative tweet that effectively communicated the value of their product to a wider audience.

## Bonus: Add a custom video preview

Dub also supports the `og:video` [OpenGraph](https://ogp.me/) tag, which allows you to set a custom video preview for your links.

To set a custom video preview, you can pass a link to your video via the `video` attribute when [creating a link](/docs/api-reference/links/create) using our API:

```typescript title="create-link.ts" theme={null}
import { Dub } from "dub";

const dub = new Dub();

await dub.links.create({
  url: "https://example.com",
  proxy: true, // proxy has to enabled for custom link previews to work
  image: "https://example.com/image.png", // it's recommended to pass a fallback image for platforms that don't support og:video
  video: "https://example.com/video.mp4", // [!code highlight]
});
```

<Info>
  We currently do not support uploading custom video previews directly via the
  dashboard, but please [let us know](https://dub.co/contact/support) if that's
  something that you'd like to see.
</Info>

Here's a demo of how custom video previews works – let's embed a link ([https://dub.link/og-video](https://dub.link/og-video)) that has custom video preview enabled in Notion:

<Frame>
  <video />
</Frame>

As you can see, the link unfurled into a video preview when embedded in Notion, but if you clicked on the link, it would redirect you to the destination URL.

## Why are my custom link previews not working?

If your custom link previews are not working, it's usually due to a caching issue, where the social media platform cached the old version of your link.

You can use the following tools to bust the cache:

* **X**: [cards-dev.x.com/validator](https://cards-dev.x.com/validator)
* **LinkedIn**: [linkedin.com/post-inspector](https://www.linkedin.com/post-inspector)
* **Facebook**: [developers.facebook.com/tools/debug](https://developers.facebook.com/tools/debug)

If you're still having issues, please [let us know](https://dub.co/contact/support) and we'll be happy to help you out.


# Custom QR codes
Source: https://dub.co/help/article/custom-qr-codes

Learn how custom QR codes work on Dub.

With Dub you customize your short link QR codes, giving you many options to make it your own. This can be done during link creation or editing your link later.

<Frame>
  <img alt="Default QR Code editor" />
</Frame>

## Logo controls

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

With a paid plan, Dub gives you the ability to hide or show your logo on the QR code. For free users, the Dub logo will be shown by default and cannot be removed.

<Frame>
  <img alt="QR Code logo controls" />
</Frame>

If you'd like to remove the Dub logo/upload your own, please consider [upgrading to a Pro plan](https://dub.co/pricing#how-to-upgrade-to-pro) to support us. Thank you!

### Setting a custom QR code logo

There are two ways to set a custom QR code logo:

* [For your entire workspace](#set-a-custom-qr-code-logo-for-your-entire-workspace) – applies to all short links under your workspace
* [For a specific domain](#set-a-custom-qr-code-logo-for-a-specific-domain) – applies to all short links under this domain

<Tip>
  If a custom QR code logo is set for both your workspace and a domain, the
  custom QR code logo for the domain will take precedence.
</Tip>

#### Option 1: Set a custom QR code logo for your entire workspace

To set a custom QR code logo for your entire workspace, you'll need to set your workspace's logo first by following the steps below:

1. Go to your [workspace settings page](https://app.dub.co/settings) by clicking on the **Settings** tab in the sidebar.
2. Scroll down to the **Workspace Logo** section and upload your company's logo. We recommend using a square image for the best results.

<Frame>
  <img alt="Workspace Logo section" />
</Frame>

Once you've set your workspace's logo, your QR code will automatically update to use your workspace's logo.

#### Option 2: Set a custom QR code logo for a specific domain

If you manage multiple brands/products on Dub, you can set custom QR code logos for each of them for better brand recognition.

<Frame>
  <img alt="Set custom QR code logos for your domains" />
</Frame>

To set a custom QR code logo for a specific domain, you'll need to follow the steps below:

1. Go to your [workspace Domains settings tab](https://app.dub.co/domains).
2. Click on the `⋮` button next to the domain you want to set a custom not found URL for.
3. Select **Edit Domain** to edit the domain settings.
4. Toggle the **Custom QR code logo** switch to enable it.
5. Upload your custom QR code logo. We recommend using a square image for the best results.
6. Click on the **Save changes** button to save the changes.

<Frame>
  <img alt="Setting a custom QR code logo for a custom domain" />
</Frame>

## QR code styling

Dub gives you the ability to customize the style of your QR code. You can customize the dot style, marker center, marker border, and colors of your QR code.

### Dot style

To change the dot style of your QR code, click on one of the three options below the QR code preview. You can choose between square, circles, and joined dot styles. The default dot style is square.

<Frame>
  <img alt="QR code dot style options" />
</Frame>

### Marker center

To change the marker center style of your QR code, click on one of the two options below the QR code preview. You can choose between square, or circle center styles. The default marker center style is square.

<Frame>
  <img alt="QR code marker center style options" />
</Frame>

### Marker border

To change the marker border style of your QR code, click on one of the three options below the QR code preview. You can choose between square, rounded, or circle border styles. The default marker border style is square.

<Frame>
  <img alt="QR code marker border style options" />
</Frame>

### Colors

Dub gives you the ability to edit both the dot and marker colors of your QR code. To change the colors of your QR code, you can either enter a custom hex color code or use the color palette picker. The default dot and marker color is black (#000000).

<Frame>
  <img alt="QR code color options" />
</Frame>

By default, the dot and marker colors will be synced, but if you select a different color for the marker, they will be unsynced. If you want to sync the colors again, simply click on the **Match dot color** button above the marker color picker.

<Frame>
  <img alt="QR code color match" />
</Frame>

## Programmatically creating QR codes

Once you've set a custom QR code logo, you can use our [QR Code API](/docs/api-reference/endpoint/retrieve-a-qr-code) to create QR codes programmatically as well.

On our Pro plans and above, you can choose to include your logo in the generated QR codes or hide it if you want.

* If your workspace has a logo set, your QR codes will automatically have that logo as well, but you can choose to hide it using the `hideLogo` prop ([example](https://api.dub.co/qr?url=https://d.to/try?qr=1))
* If your workspace has no logo set, your QR codes will have no logo.
* Alternatively, you can also pass a custom logo in your QR codes using the `logo` prop ([example](https://api.dub.co/qr?url=https://d.to/try?qr=1\&logo=https://assets.dub.co/wordmark-square.png)).

## Downloading you QR code

To download your QR code, click the **Download** button in the top right of the editor and select your format. You're able to download your QR code in SVG, PNG, or JPG formats.

<Frame>
  <img alt="Downloading your QR code" />
</Frame>

## Copying your QR code

To copy your QR code, click the **Copy** button in the top right of the editor. You're able to copy the image version of your QR code, or the URL of the QR code image.

<Frame>
  <img alt="Copying your QR code" />
</Frame>


# Customer insights
Source: https://dub.co/help/article/customer-insights

Get deeper, real-time insights about your customers' demographics, purchasing behavior, and lifetime value (LTV).

One of the most important parts of marketing is accurate attribution – understanding how your marketing efforts and spend are converting to customer acquisition and revenue.

With Dub, you can turn your short links into end-to-end attribution engines by tracking how your clicks convert to [signups](/docs/quickstart/server#tracking-lead-events) and [sales](/docs/quickstart/server#tracking-sale-events).

As part of lead tracking, Dub creates customer objects to visualize how your users interact with your links and how much they spend on your product.

Now with **Customer Insights**, you can get a deeper understanding of your customers' demographics, purchasing behavior, and lifetime value (LTV).

<Frame>
  <img alt="Customer insights on Dub" />
</Frame>

## Customers overview

Your Dub workspace now has a new **Customers** tab that shows you a list of all your customers and which short link they came from.

<Frame>
  <img alt="Customers table on Dub" />
</Frame>

In this table, you can also filter by country or a specific link, or search for a specific customer by their name, email, or [external ID](/docs/api-reference/endpoint/track-lead#body-external-id).

### Available columns

The data columns available to you are:

| Column      | Description                                                      |
| ----------- | ---------------------------------------------------------------- |
| Country     | The customers country                                            |
| Link        | The short link used to visit your domain                         |
| LTV         | The total amount of revenue the customer has generated over time |
| Created     | The date the customer was created                                |
| Paid        | The date the customer made their first sale                      |
| Cancelled   | The date the customer canceled their subscription                |
| External ID | The unique identifier within your database                       |

## Individual customer pages

For a given customer, you can also open up their individual customer page to see a view of their recent sales and historical activity.

<Frame>
  <img alt="Customers page on Dub" />
</Frame>

This is useful for understanding which of your links a given customer interacted with before signing up for your app, allowing you to optimize your campaigns accordingly.

On the top of each page, you're able to see the customer's first sale date, time to sale, lifetime value, and if they are no longer a customer, their canceled date.

On the right sidebar, you can also see all the individual details for a given customer – from their geolocation, device type, and browser info, and which UTM campaigns they originated from.

## Partner program customers

If the customer was referred by a partner, you'll be able to view which partner referred them, the partner's earnings and which link was used.

Learn more about [Dub Partner Programs](https://dub.co/partners).

<Frame>
  <img alt="Partner referral customer pages on Dub" />
</Frame>


# What are default Dub domains?
Source: https://dub.co/help/article/default-dub-domains

Learn more about the default branded domains provided by Dub and how you can use them to share your links.

Dub is the only link management platform that provides [default branded domains](https://app.dub.co/settings/domains/default) for you to use. These domains are automatically set up for you when you create a new account on Dub.

<DefaultDomainsSlider />

## List of default branded domains

Dub offers the following default branded domains:

1. `dub.sh`: This domain is the default domain for all new Dub accounts. You can use this domain to shorten any link you want to share.
2. `spti.fi`: Branded domain for Spotify links (songs, playlists, etc.).
3. `git.new`: Branded domain for GitHub links (repositories, gists, etc.).
4. `ggl.link`: Branded domain for Google links (search, docs, sheets, slides, drive, maps, etc.).
5. `chatg.pt`: Branded domain for ChatGPT links (convos, custom GPTs).
6. `amzn.id`: Branded domain for Amazon links (products, wishlists, etc.).

You can use these domains to shorten links related to the respective platforms. For example, if you want to share a Spotify link, you can use the `spti.fi` domain to shorten it.

## Dub.sh links vs custom domain links

Once you create a workspace on Dub, you have the option to choose between the default `dub.sh` domain or [your own custom domain](/help/article/how-to-add-custom-domain) to create short links.

Here are the difference between the two options:

|                | Default `dub.sh` links                                            | Custom domain links                                                                                                             |
| -------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Setup ease** | No setup required – you can start creating short links right away | You will need to [set up your custom domain](/help/article/how-to-add-custom-domain) before you can start creating short links. |
| **Branding**   | Dub branding on the short link                                    | Your own branding on the short link                                                                                             |


# How to use device targeting on Dub?
Source: https://dub.co/help/article/device-targeting

Learn how to use device targeting on Dub to personalize the destination URL for your links based on the user's device type.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

Device targeting is a powerful feature that lets you personalize the destination URL for your links based on the user's device type.

For example, you can set a custom destination URL for iOS devices by toggling the "iOS Targeting" switch in the [link builder](/help/article/dub-link-builder), and a different URL for Android devices by toggling the "Android Targeting" switch in the link builder.

To use this feature, click on the **Targeting** button in the link builder. This will open a new modal with device targeting options.

You can also quickly access the Targeting feature by using the keyboard shortcut `G` while in the link builder.

<Frame>
  <img alt="Use device targeting to personalize your link's behavior based on the user's device type" />
</Frame>

This is particularly useful if you want to send iOS users to the App Store to download your iOS app, and Android users to the Google Play Store to download your Android app.

In both cases, the main **Destination URL** will be used as the fallback URL that will be used if the user's device type does not match the targeting rules.

## Priority when combining with other features

If you enable multiple targeting features on the same link, they are evaluated in the following order:

1. **Device targeting** (iOS/Android) – checked first
2. **[Geo targeting](/help/article/geo-targeting)** – checked next if no device rule matches
3. **[A/B testing](/help/article/ab-testing)** – applied last, only to traffic not already redirected by device or geo targeting

This means device targeting always takes the highest priority. For example, if a user on iOS clicks your link and you have an iOS destination URL configured, they will be sent there regardless of any geo targeting or A/B testing rules.


# Offering dual-sided incentives
Source: https://dub.co/help/article/dual-sided-incentives

Learn how to offer dual-sided incentives to your partners and the users they refer.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

With [Dub Partners](https://dub.co/partners), you can create dual-sided incentives for your affiliate/referral programs, which give special discounts to customers who sign up via a referral link.

<Frame>
  <video />
</Frame>

Some examples include:

* 25% discount for the first 12 months
* 30% lifetime discount
* \$50 one-off discount

This can drive powerful word-of-mouth growth as partners are more likely to share their link if it gives their audience/user base additional discounts, and on the other hand, their users are more likely to click on their links as well if they're getting a special deal.

In this article, we'll learn how to set up dual-sided incentives with Dub Partners.

## Option 1: Direct link-based discounts (recommended)

If you're using Stripe for payments, you can follow these steps to set up direct link-based discounts for your partner referral links:

<Note>
  Link-based discounts provide **better attribution accuracy** since you get
  visibility into the customer's geolocation, device info, referrer details, and
  UTM data.

  The trade-off here is that it requires some engineering work to set up.
</Note>

<Steps>
  <Step title="Create a discount on Dub">
    First, navigate to the partner group that you want to create a discount for. Under the Discount tab, you'll be able to create a discount for the group

    <Frame>
      <img alt="Group discounts page" />
    </Frame>

    If you already have a discount set up for your default group, you can just duplicate it. If not, click Create to create your first group discount:

    <Frame>
      <img alt="Program discounts tab" />
    </Frame>

    **New Stripe coupon**

    If you don't have a coupon set up on Stripe yet, you can use the New Stripe coupon option to create a new coupon based on the discount type (percentage vs flat), amount, and duration set in Dub.

    **Use Stripe coupon ID**

    If you already have an existing coupon on Stripe, you can enter the Stripe coupon ID (should be an 8 alphanumeric code) under the Use Stripe coupon ID option.

    <Warning>
      Since Stripe doesn't support updating coupons after creation, you'd need to delete your discount in Dub and create a new one if you want to update any of the discount parameters.

      Note: Deleting a discount on Dub does not delete the corresponding Stripe coupon.
    </Warning>
  </Step>

  <Step title="Add coupon logic to checkout flow">
    The key to implementing link-based discounts is to check if a customer is eligible for a discount using Dub's Customers API, then automatically apply the appropriate Stripe coupon to their checkout session.

    Here's how the flow works:

    1. When a user clicks a partner referral link, Dub tracks them as a potential customer
    2. During checkout, you query the Dub Customers API using the user's ID
    3. If they're eligible for a discount, you apply the coupon to their Stripe checkout session
    4. If not, you allow them to enter promotion codes manually

    <CodeGroup>
      ```ts TypeScript expandable theme={null}
      import { dub } from "@/lib/dub.ts";
      import Stripe from "stripe";

      const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

      // Get customer discount eligibility from Dub
      const customers = await dub.customers.list({
        externalId: userId, // their user ID within your app
        includeExpandedFields: true,
      });

      const customerDiscount = customers.length > 0 ? customers[0].discount : null;

      // Create Stripe checkout session with conditional discount
      const stripeSession = await stripe.checkout.sessions.create({
        success_url: "https://app.domain.com/upgraded",
        cancel_url: "https://app.domain.com/pricing",
        line_items: [
          {
            price: "price_1MotwRLkdIwHu7ixYcPLm5uZ",
            quantity: 1,
          },
        ],
        mode: "subscription", // or "payment" for one-time purchases
        ...(customerDiscount
          ? {
              // Apply discount automatically if customer is eligible
              discounts: [
                {
                  coupon:
                    process.env.NODE_ENV !== "production" &&
                    customerDiscount.couponTestId
                      ? customerDiscount.couponTestId
                      : customerDiscount.couponId,
                },
              ],
            }
          : {
              // Allow manual promo code entry if no automatic discount
              allow_promotion_codes: true,
            }),
        customer_email: userEmail,
        metadata: {
          userId: userId,
          ...(customerDiscount && { dubDiscountId: customerDiscount.id }),
        },
      });
      ```

      ```python Python expandable theme={null}
      import stripe
      from dub import Dub
      import os

      stripe.api_key = os.environ.get("STRIPE_SECRET_KEY")
      dub_client = Dub(token=os.environ.get("DUB_API_KEY"))

      user_id = "user_123"  # the user's ID in your app

      # Get customer discount eligibility from Dub
      customers = dub_client.customers.list(
          email=None,
          external_id=user_id,
          include_expanded_fields=True
      )

      # Prepare checkout session parameters
      session_params = {
          "success_url": "https://app.domain.com/upgraded",
          "cancel_url": "https://app.domain.com/pricing",
          "line_items": [{"price": "price_1MotwRLkdIwHu7ixYcPLm5uZ", "quantity": 1}],
          "mode": "subscription",  # or "payment" for one-time purchases
          "customer_email": user_email,
          "metadata": {
              "userId": user_id,
          },
      }

      # Apply discount if customer is eligible
      if customers and hasattr(customers[0], "discount") and customers[0].discount and customers[0].discount.coupon_id:
          coupon_id = customers[0].discount.coupon_test_id if (
              os.environ.get("NODE_ENV") != "production"
          ) and hasattr(customers[0].discount, "coupon_test_id") else customers[0].discount.coupon_id

          session_params["discounts"] = [{"coupon": coupon_id}]
          session_params["metadata"]["dubDiscountId"] = customers[0].discount.id
      else:
          session_params["allow_promotion_codes"] = True

      # Create the checkout session
      session = stripe.checkout.Session.create(**session_params)
      ```

      ```ruby Ruby expandable theme={null}
      require 'stripe'
      require 'dub'

      # Initialize clients
      Stripe.api_key = ENV['STRIPE_SECRET_KEY']

      dub_client = ::OpenApiSDK::Dub.new
      dub_client.config_security(
        ::OpenApiSDK::Shared::Security.new(
          token: ENV['DUB_API_KEY'],
        )
      )

      # Get customer discount eligibility from Dub
      user_id = "user_123" # the user's ID in your app
      customers = dub_client.customers.list(
        ::OpenApiSDK::Operations::ListCustomersRequest.new(
          request: ::OpenApiSDK::Operations::ListCustomersRequestBody.new(
            external_id: user_id,
            include_expanded_fields: true
          )
        )
      )

      # Create checkout session parameters
      session_params = {
        success_url: 'https://app.domain.com/upgraded',
        cancel_url: 'https://app.domain.com/pricing',
        line_items: [
          {
            price: 'price_1MotwRLkdIwHu7ixYcPLm5uZ',
            quantity: 1,
          },
        ],
        mode: 'subscription', # or 'payment' for one-time purchases
        customer_email: user_email,
        metadata: {
          userId: user_id,
        },
      }

      # Apply discount if customer is eligible
      if customers.respond_to?(:customers) &&
        !customers.customers.empty? &&
        customers.customers[0].discount &&
        customers.customers[0].discount.coupon_id

        coupon_id = if ENV['NODE_ENV'] != 'production' &&
                      customers.customers[0].discount.coupon_test_id
                      customers.customers[0].discount.coupon_test_id
                    else
                      customers.customers[0].discount.coupon_id
                    end

        session_params[:discounts] = [{ coupon: coupon_id }]
        session_params[:metadata][:dubDiscountId] = customers.customers[0].discount.id
      else
        session_params[:allow_promotion_codes] = true
      end

      # Create the checkout session
      session = Stripe::Checkout::Session.create(session_params)
      ```

      ```go Go expandable theme={null}
      package main

      import (
          "context"
          "os"

          "github.com/stripe/stripe-go/v72"
          "github.com/stripe/stripe-go/v72/checkout/session"
          "github.com/dubinc/go-sdk/dub"
          "github.com/dubinc/go-sdk/operations"
      )

      func main() {
          // Initialize clients
          stripe.Key = os.Getenv("STRIPE_SECRET_KEY")

          dubClient := dub.New(
              dub.WithSecurity(os.Getenv("DUB_API_KEY")),
          )

          // Get customer discount eligibility from Dub
          userId := "user_123" // the user's ID in your app
          ctx := context.Background()

          listRequest := operations.ListCustomersRequest{
              Request: &operations.ListCustomersRequestBody{
                  ExternalId: &userId,
                  IncludeExpandedFields: true,
              },
          }

          customers, err := dubClient.Customers.List(ctx, listRequest)
          if err != nil {
              // Handle error
              return
          }

          // Create checkout session parameters
          params := &stripe.CheckoutSessionParams{
              SuccessURL: stripe.String("https://app.domain.com/upgraded"),
              CancelURL:  stripe.String("https://app.domain.com/pricing"),
              LineItems: []*stripe.CheckoutSessionLineItemParams{
                  {
                      Price:    stripe.String("price_1MotwRLkdIwHu7ixYcPLm5uZ"),
                      Quantity: stripe.Int64(1),
                  },
              },
              Mode:          stripe.String(string(stripe.CheckoutSessionModeSubscription)),
              CustomerEmail: stripe.String(userEmail),
          }

          // Add metadata
          params.AddMetadata("userId", userId)

          // Apply discount if customer is eligible
          if len(customers.Customers) > 0 && customers.Customers[0].Discount != nil && customers.Customers[0].Discount.CouponId != nil {
              var couponId string
              if os.Getenv("NODE_ENV") != "production" && customers.Customers[0].Discount.CouponTestId != nil {
                  couponId = *customers.Customers[0].Discount.CouponTestId
              } else {
                  couponId = *customers.Customers[0].Discount.CouponId
              }

              params.Discounts = []*stripe.CheckoutSessionDiscountParams{
                  {
                      Coupon: stripe.String(couponId),
                  },
              }
              params.AddMetadata("dubDiscountId", *customers.Customers[0].Discount.Id)
          } else {
              params.AllowPromotionCodes = stripe.Bool(true)
          }

          // Create the checkout session
          s, err := session.New(params)
          if err != nil {
              // Handle error
              return
          }
      }
      ```
    </CodeGroup>

    Once this is set up, eligible customers will automatically see the coupon code applied at checkout:

    <Frame>
      <img alt="Stripe coupon applied" />
    </Frame>
  </Step>
</Steps>

## Option 2: Using Stripe promo codes (no code required)

If you prefer a no-code solution, you can set up Stripe promo-code-based discounts for your partners.

<Note>
  Stripe promo-code-based discounts is much easier to set up (no code required).

  However, you do sacrifice on attribution accuracy since you won't have any
  insights into the customer's geolocation, device info, referrer details, and
  UTM data. Our Stripe integration will try to derive the customer's location
  based on their Stripe billing address, but it can sometimes be inaccurate.
</Note>

<Steps>
  <Step title="Create a discount on Dub">
    First, navigate to the partner group that you want to create a discount for. Under the Discount tab, you'll be able to create a discount for the group

    <Frame>
      <img alt="Group discounts page" />
    </Frame>

    If you already have a discount set up for your default group, you can just duplicate it. If not, click Create to create your first group discount:

    <Frame>
      <img alt="Program discounts tab" />
    </Frame>

    **New Stripe coupon**

    If you don't have a coupon set up on Stripe yet, you can use the New Stripe coupon option to create a new coupon based on the discount type (percentage vs flat), amount, and duration set in Dub.

    **Use Stripe coupon ID**

    If you already have an existing coupon on Stripe, you can enter the Stripe coupon ID (should be an 8 alphanumeric code) under the Use Stripe coupon ID option.

    <Warning>
      Since Stripe doesn't support updating coupons after creation, you'd need to delete your discount in Dub and create a new one if you want to update any of the discount parameters.

      Note: Deleting a discount on Dub does not delete the corresponding Stripe coupon.
    </Warning>

    ### Auto-provision discount codes

    When enabled, discount codes will be automatically created for all existing partners in this group and future partners when they join this group.

    <Frame>
      <img alt="Auto provision discount codes" />
    </Frame>
  </Step>

  <Step title="Create your partner code">
    Open the partner profile that you'd like to create a discount code for, then click Create Code in the "Discount codes" section.

    <Frame>
      <img alt="Partner links page" />
    </Frame>

    Here you can select which referral link you'd like to associate the code with. Then you can create the discount code.

    <Frame>
      <img alt="Partner links page" />
    </Frame>

    <Warning>
      Discount codes cannot be edited after creation, so ensure that you have
      everything correct before creating the code
    </Warning>

    After the code has been created, you'll see its value populated in the Discount Code section, and it is ready for use. In the Partner Dashboard, they'll also see the associated discount code within their partner links.

    <Frame>
      <img alt="Partner links page with discount code" />
    </Frame>
  </Step>
</Steps>

## Displaying discount banner

Once you've set up dual-sided incentives, a potential next step would be to display the discount information on your pricing page / landing page hero.

To do that, all you need is to install the [Dub Analytics script](/docs/sdks/client-side/introduction) with client-side click-tracking enabled – then, when someone lands on your site via a valid referral link, the script will automatically fetch the partner and discount data for you.

This data will be stored as a JSON-stringified object in the `dub_partner_data` cookie in the following format:

```json theme={null}
{
  "clickId": "xxx", // unique ID of the click event
  "partner": {
    "id": "pn_xxx", // unique ID of the partner on Dub
    "name": "John Doe", // name of the partner
    "image": "https://example.com/john.png", // avatar of the partner
    "groupId": "grp_xxx", // unique ID of the group the partner belongs to
    "tenantId": "user_xxx" // partner's unique ID in your database
  },
  "discount": {
    "id": "disc_xxx", // unique ID of the discount on Dub
    "amount": 25, // discount amount (either a percentage or a fixed amount)
    "type": "percentage", // type of the discount (either "percentage" or "fixed")
    "maxDuration": 3, // maximum duration of the discount in months
    "couponId": "XZuejd0Q", // Stripe coupon code
    "couponTestId": "2NMXz81x" // Stripe test coupon ID
  }
}
```

You can access partner and discount data in your application code using the `useAnalytics()` hook. If you’re working in a non-React environment, you can use the `DubAnalytics` object directly.

Here is a quick example of how you can display a discount banner using the `useAnalytics()` hook:

<CodeGroup>
  ```javascript HTML theme={null}
  // Display a banner that says: _"John referred you to Acme and gave you 25% off"_
  dubAnalytics("ready", function () {
    if (DubAnalytics.partner && DubAnalytics.discount) {
      const banner = document.createElement("div");

      banner.innerHTML = `
        <div class="flex items-center gap-2">
          <img src="${DubAnalytics.partner.image}" alt="${DubAnalytics.partner.name}" class="size-6 rounded-full" />
          <p>${DubAnalytics.partner.name} referred you to Acme and gave you ${DubAnalytics.discount.amount} ${DubAnalytics.discount.type} off</p>
        </div>
      `;
      document.body.appendChild(banner);
    }
  });
  ```

  ```typescript React/Next.js theme={null}
  // Display a banner that says: _"John referred you to Acme and gave you 25% off"_
  import { useAnalytics } from "@dub/analytics/react";

  function DiscountBanner() {
    const { partner, discount } = useAnalytics();

    if (!partner || !discount) {
      return null;
    }

    return (
      <div className="flex items-center gap-2">
        <img
          src={partner.image}
          alt={partner.name}
          className="size-6 rounded-full"
        />
        <p>
          {partner.name} referred you to Acme and gave you {discount.amount}{" "}
          {discount.type} off
        </p>
      </div>
    );
  }
  ```
</CodeGroup>

Here's an example of how the discount banner will look like:

<Frame>
  <img alt="A screenshot of the Dub partner data example" />
</Frame>


# Dub Analytics Overview
Source: https://dub.co/help/article/dub-analytics

Learn about how you can use Dub Analytics to better understand your audience.

On any given day, Dub Analytics tracks upwards of **10 million clicks and [conversion events](/help/article/dub-conversions)**.

With the sheer volume of data that's tracked, it is important to be able to easily filter and create reports to help marketing teams make informed decisions.

In this guide, we'll walk you through how to use Dub Analytics to get the most out of your click events and engagement data.

## Live Demo

To give you some context before we dive in, here's a live demo of Dub's Analytics dashboard:

<ImageLink alt="Dub's Analytics dashboard" href="https://d.to/stats/try" />

<Tip>
  This public page was created using Dub's shareable link feature. You can
  create your own shareable link by following the steps in the [Share your
  analytics](/help/article/share-analytics) guide.
</Tip>

## Analytics Views

Dub's Analytics dashboard consists of several views:

1. [Time-series analytics chart](#1-time-series-analytics-chart)
2. [Aggregated data for different facets (top views)](#2-aggregated-data-for-different-facets-top-views)
3. [Real-time events stream](#3-real-time-events-stream)

### 1. Time-series analytics chart

This is the default view and shows you the number of click events over time.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

### 2. Aggregated data for different facets (top views)

These are more commonly known as the "Top Views" in Dub Analytics. These views show you the top links, countries, cities, devices, and more.

<Frame>
  <img alt="Aggregated data for different facets" />
</Frame>

### 3. Real-time events stream

Dub also offers a [Real-time Events Stream](/help/article/real-time-events-stream) view that shows you the events that are happening in real-time. These events are sorted by the time they occurred, so you can see the most recent events first.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

The real-time events stream lives in your workspace's [**Events** tab](https://app.dub.co/events), but you can also access it via the **View Events** button in the Analytics tab:

<Frame>
  <img alt="Quick link to open-up the events stream from the Analytics tab" />
</Frame>

<Tip>
  When you click on the **View Events** button, the same filters you have
  applied in the Analytics tab are applied to the Events tab as well.
</Tip>

## Date Range Picker

Dub Analytics comes with a powerful date range picker that allows you to select custom date ranges for your reports.

This is especially useful when you want to compare data over different time periods, or when you want to focus on a specific time frame that is not covered by the preset date ranges.

<Frame>
  <img alt="New Date Range Picker for custom date ranges" />
</Frame>

<Tip>
  Pro-tip: You can use keyboard shortcuts to toggle between different preset date ranges:

  * `D` – Last 24 hours
  * `W` – Last 7 days
  * `T` – Last 30 days
  * `3` – Last 3 months
  * `L` – Last 12 months
  * `M` – Month to Date
  * `Q` – Quarter to Date
  * `Y` – Year to date
  * `A` – All time
</Tip>

## Filtering data

Dub's analytics dashboard comes with a sleek and keyboard-friendly filter bar at the top of the dashboard that you can use to filter your data.

<Frame>
  <video />
</Frame>

Here are some of the filter facets you can use:

* **Domain** – Filter by domain (e.g. `dub.sh`, `git.new`, `spti.fi`)
* **Tags** – [Filter by tags](/help/article/filter-analytics-by-tags) (e.g. Social Media, Email Campaign, Blog Post)
* **Folders** – [Filter by folders](/help/article/filter-analytics-by-folders) (e.g. Partner links, Marketing links)
* **Trigger** – [Filter by event trigger](/help/article/filter-analytics-by-trigger) (e.g. link click, QR code scan)
* **Device** – Filter by device type (e.g. Mobile, Desktop, Tablet)
* **Country** – Filter by country (e.g. United States, India, Germany)
* **City** – Filter by city (e.g. San Francisco, New York, London)
* **Region** – Filter by region (e.g. California, New York, London)
* **Continent** – Filter by continent (e.g. North America, Europe, Asia)
* **Browser** – Filter by browser (e.g. Chrome, Safari, Firefox)
* **OS** – Filter by operating system (e.g. iOS, Android, Windows)
* **Referrer** – Filter by referrer (e.g. Direct, Google, Facebook)
* **UTM Parameters** – [Filter by UTM parameters](/help/article/filter-analytics-by-utms) (e.g. `utm_source`, `utm_content`)

Additionally, with [Dub Partners](https://dub.co/partners), you can also filter by the following:

* **Partner** – Filter by [individual partners](/help/article/managing-program-partners) to measure partner activity and ROI across different time ranges
* **Partner Group** – Filter by [partner group](/help/article/partner-groups) (e.g. "Influencers" vs "User Referrals" vs "Affiliates" vs "Publishers") to understand the ROI on each of your partnership channels

<Tip>
  Pro-tip: You can use the `F` keyboard shortcut to open up the filter bar.
</Tip>

## Advanced analytics filters

You can also use our built-in advanced filtering capabilities to create powerful, customized reports for your marketing campaigns:

### Multi-filtering ("IS ONE OF")

If you need to pull stats for multiple entities at once, you can now do so with our multi-filtering feature:

<Frame>
  <img alt="Advanced filters IS" />
</Frame>

Examples:

* Filtering by multiple [partners](/help/article/managing-program-partners)
* Filtering by multiple [partner groups](/help/article/partner-groups)
* Filtering by multiple [folders](/help/article/filter-analytics-by-folders)
* Filtering by multiple [tags](/help/article/filter-analytics-by-tags)

### Negative filtering ("IS NOT")

Additionally, you can also pull stats for all entities \*\*\*except\*\*\* a select few to make more reporting more robust.

<Frame>
  <img alt="Advanced filters IS NOT" />
</Frame>

Examples:

* Filtering for non-US traffic
* Filtering for all [partners](/help/article/managing-program-partners) except a few outliers
* Filtering for all [partner groups](/help/article/partner-groups) except for "User referrals"
* Filtering for all [tags](/help/article/filter-analytics-by-tags) except for "General links"

Best part? You can also mix and match both multi-filtering and negative-filtering as well:

<Frame>
  <img alt="Mix and match analytics filters" />
</Frame>

This is available for both [Dub Partners](https://dub.co/partners) & [Dub Links](https://dub.co/links), and you can filter across [all of the facets mentioned above](#filtering-data).

Check out [an example here](https://app.dub.co/share/dash_6NSA6vNm017MZwfzt8SubNSZ?interval=30d\&country=-IN%2CVN).

## Exporting your analytics data

You can also [export your analytics data](/help/article/how-to-export-analytics) or [events stream data](/help/article/real-time-events-stream#filtering-and-exporting-events) to a CSV file at any time. This can be useful if you want to analyze the data in a business intelligence (BI) tool, share it with your team, or import it into a spreadsheet.

<Frame>
  <img alt="Click on the download icon to export the data" />
</Frame>

## Bonus: "Ask AI" feature

You can also use the "Ask AI" feature to query your analytics with natural language.

For example, you can ask questions like:

* "mobile chrome users US only"
* "QR scans last quarter"
* "UK android users"
* "[filter for new sales only](https://dub.co/changelog/filter-sale-type) last 30 days"

And [Dub AI](https://dub.co/blog/introducing-dub-ai) will automatically select the right filters and generate a report for you.

<Frame>
  <video />
</Frame>


# Dub Analytics Limits
Source: https://dub.co/help/article/dub-analytics-limits

Learn about the different limits for Dub Analytics and how many tracked events you can track on each plan.

Dub has one of the most generous analytics limits of any link management platform.

With [Dub Analytics](/help/article/dub-analytics), you can track up to 1,000 tracked events per month for free – no credit card required. You also get access to advanced analytics features like:

* Breakdown by location (country, city, continent)
* Breakdown by device (type, browser, operating system)
* Top links, UTM parameters, and referrer data
* [AI filtering](/help/article/dub-analytics#bonus-ask-ai-feature) feature
* Filtering by [tags](/help/article/filter-analytics-by-tags), [QR code vs link tracked events](/help/article/filter-analytics-by-trigger) and more
* Real-time [analytics-sharing](/help/article/share-analytics)

## Dub Analytics limits by plan

Here are the tracked events limits for each plan on Dub:

| Plan       | Limit                                                                       |
| ---------- | --------------------------------------------------------------------------- |
| Free       | 1,000 tracked events/mo                                                     |
| Pro        | 50,000 tracked events/mo (or 600,000 tracked events/yr on a yearly plan)    |
| Business   | 250,000 tracked events/mo (or 3,000,000 tracked events/yr on a yearly plan) |
| Advanced   | 1,000,000 tracked events/mo (or 12,000,000 events/yr on a yearly plan)      |
| Enterprise | Custom                                                                      |

On Dub's paid plans, you also have access to advanced features like:

* [Analytics API](/docs/api-reference/endpoint/retrieve-analytics) – Pro plan and above
* [Real-time events stream](/help/article/real-time-events-stream) – Business plan and above
* [Event Webhooks](/docs/webhooks/introduction) – Business plan and above
* [Dub Partners](/help/article/dub-partners) – Business plan and above

<Note>
  If you don't have a destination URL configured on your [root domain
  link](/help/article/how-to-redirect-root-domain), tracked events on your root
  domain link won't count towards your monthly tracked events limit. Learn more
  [here](/help/article/how-to-redirect-root-domain).
</Note>

## What happens when I exceed the tracked events limit on my plan?

If you exceed your monthly tracked events limits, your existing links will still work and we will continue to track events on them. However, you will need to [upgrade your plan](https://app.dub.co/upgrade) to view their analytics.

You will receive two email notifications when you exceed your tracked events limit – once when you first exceed the limit, and once more in 3 days if you still have not upgraded your plan.

We currently do not bill for overages. However, to prevent abuse, if you exceed your tracked events limit by a certain threshold, your tracked events will not be reset at the start of your billing period.

Here are the thresholds for each plan:

| Plan            | Threshold                                           |
| --------------- | --------------------------------------------------- |
| Free            | 4x your plan's monthly limit (4,000 tracked events) |
| All other plans | 2x your plan's monthly limit                        |

When that happens, you can [upgrade your plan](https://app.dub.co/upgrade) to reset your tracked events limit.

<Warning>
  Excessive events usage that violates our [fair usage
  policy](https://dub.co/legal/terms#3-fair-use) may result in your links being
  disabled to protect the integrity of our platform.
</Warning>


# Conversion tracking
Source: https://dub.co/help/article/dub-conversions

Learn how you can use Dub to track how your link clicks are converting to signups and sales.

<Tip>
  This feature is only available on [Dub Business](https://dub.co/pricing) and
  above.
</Tip>

Data is the lifeblood of any business. It helps you understand your customers, make data-driven decisions, and drive growth.

Whenever you run a marketing campaign, merely tracking link clicks is not sufficient to paint a complete picture of your customer acquisition funnel.

You need to track how those clicks are converting to signups & sales to be able to determine which campaigns/channels are performing the best.

**This is where Dub comes in.**

With Dub, you can turn any [short link you create on Dub](/help/article/how-to-create-link) into a full attribution engine. This allows you to understand how well your links are translating to actual users and revenue dollars.

<VideoPlayer title="Watch Dub conversion tracking in action" />

## How does conversion tracking work on Dub?

The way conversion tracking on Dub works can be boiled down to 3 simple steps:

<Steps>
  <Step title="Click Event">
    This is the first step in the conversion funnel. It can either be:

    * A link click on a social media post/ad
    * A link click in an email/SMS
    * A QR code scan for physical (or video) campaigns
  </Step>

  <Step title="Lead Event">
    When a user performs an action that indicates interest in your product or
    service. This could be anything from:

    * Signing up for an account
    * Adding a product to cart
    * Joining a mailing list
  </Step>

  <Step title="Sale Event">
    When a user purchases your product or service. Examples include:

    * Subscribing to a paid plan
    * Usage expansion (upgrading from one plan to another)
    * Purchasing a product
  </Step>
</Steps>

With this 3-step process, marketing teams can make better decisions about which content or marketing efforts to invest more in – backed by real-world conversion data.

## How are conversions tracked?

Dub uses a simple first-party cookie and server-side event tracking to track conversions.

### Step 1: Storing click ID as a first-party cookie

When a user clicks on a conversion-enabled link, Dub generates a unique `dub_id` query parameter and stores it as a first-party cookie on your domain (e.g. `.dub.co`).

<Frame>
  <img alt="A diagram showing how click events are tracked in the conversion funnel" />
</Frame>

This allows Dub to attribute any subsequent conversion events to the original click – and by extension, **the link that was clicked on**.

To enable this, you need to [install the Dub Analytics script](/docs/sdks/client-side/installation-guides/react) in your project.

<NPMPackage name="@dub/analytics" description="Client-side SDK for tracking conversion analytics with Dub." />

This package will handle the detection of the `dub_id` query parameter and storing it as a first-party cookie.

### Step 2: Attributing conversion events to the original click

Then, when a conversion event occurs (e.g. a user signs up for an account), you can check for the `dub_id` cookie and attribute the conversion to the original click.

<Frame>
  <img alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

For example, if you're using a Next.js app with NextAuth for authentication, here's how you can [send lead conversion events for new user signups](/docs/conversions/leads/next-auth).

When sending a lead conversion event, you can include the following properties:

| Property             | Description                                                                                                              | Required |
| :------------------- | :----------------------------------------------------------------------------------------------------------------------- | :------- |
| `clickId`            | The unique `dub_id` parameter that the lead conversion event is attributed to.                                           | Yes      |
| `eventName`          | The name of the event. Example: "Sign up".                                                                               | Yes      |
| `customerExternalId` | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | Yes      |
| `customerEmail`      | The email address of the customer. If not passed, a random email address will be generated.                              | No       |
| `customerName`       | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                       | No       |
| `customerAvatar`     | The avatar URL of the customer. If not passed, a random avatar URL will be generated.                                    | No       |

The lead event will serve as the source of truth for the customer's identity and which link they came from. This means that all subsequent actions performed by the customer (e.g. upgrading their plan, purchasing a product) will automatically be attributed to the original link.

### Step 3: Sending sale events

The final step in the conversion funnel is to track sale events and attribute them to the original link click. This can be done in 3 ways:

<Frame>
  <img alt="A diagram showing how sale events are tracked in the conversion funnel" />
</Frame>

**Option 1: Stripe integration (recommended)**

At Dub, we have a [native Stripe integration](/docs/integrations/stripe) that you can set up in 2-3 clicks and automatically send sale events to Dub.

The way the integration works is as follows:

1. Install our [Stripe integration](https://app.dub.co/integrations/stripe) in your Dub workspace. For security reasons, the integration will have *read-only access* to your Stripe account.
2. When a user creates a checkout session on your app, pass the user's unique ID in your system as the `dubCustomerId` property in the `metadata` field.
3. This way, when the user completes their checkout session, Dub will receive a webhook from Stripe containing the `dubCustomerId`.
4. If the `dubCustomerId` matches a customer that came from a conversion-enabled link, Dub will track the sale as a result of that link.

**Option 2: Shopify integration**

We also have a [native Shopify integration](/docs/integrations/stripe) that you can install in 2-3 clicks and automatically track sale events with Dub.

The way the integration works is as follows:

1. Install the [Dub Shopify app](https://dub.co/integrations/shopify) in your Shopify store.
2. When a sale is made, the integration will automatically detect and track a laed + sale event in Dub.

**Option 3: Manually sending sale events**

Alternatively, if you prefer to send sale events manually, you can do so using our [server-side SDKs](/docs/sdks/overview).

We expose a `POST /track/sale` [endpoint](/docs/api-reference/endpoint/track-sale) that you can use to send sale events to Dub. Here's an example using the Dub [TypeScript SDK](/docs/sdks/typescript/overview):

```ts title="/api/billing/upgrade" theme={null}
import { Dub } from "dub";

const dub = new Dub();

await dub.track.sale({
  customerExternalId: "123",
  amount: 5900, // in cents
  paymentProcessor: "stripe",
  eventName: "Subscription creation",
});
```

Here are the properties you can include when sending a sale event:

| Property             | Description                                                 | Required |
| :------------------- | :---------------------------------------------------------- | :------- |
| `customerExternalId` | The unique ID of the customer in your system.               | Yes      |
| `amount`             | The amount of the sale in cents.                            | Yes      |
| `paymentProcessor`   | The name of the payment processor that processed the sale.  | No       |
| `eventName`          | The name of the event. Defaults to "Purchase".              | No       |
| `invoiceId`          | The unique ID of the invoice in your system.                | No       |
| `currency`           | The currency of the sale. Defaults to "usd".                | No       |
| `metadata`           | An object containing additional information about the sale. | No       |

## How do I see the conversions?

All your tracked conversions will show up on your [Analytics dashboard](/help/article/dub-analytics). We provide 3 different views to help you understand your conversions:

* **Time-series**: A [time-series view](/help/article/dub-analytics#1-time-series-analytics-chart) of the number clicks, leads and sales.

<Frame>
  <img alt="Time-series line chart" />
</Frame>

* **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

* **Real-time events stream**: A [real-time events stream](/help/article/real-time-events-stream) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## What can I build with Dub?

There are various use cases for Dub. Here are a few examples:

### 1. White-labeled affiliate/referral programs

With [Dub Partners](https://dub.co/partners) (built on top of Dub), you can track how well your affiliate/referral links are performing and [display the real-time analytics directly inside your application](/docs/partners/embedded-referrals).

For instance, here's a screenshot of our very own Dub referral program – powered by Dub – that [lives within the Dub dashboard](https://app.dub.co/referrals):

<Frame>
  <img alt="A demo of the Dub Referrals dashboard" />
</Frame>

With Dub's [real-time webhooks feature](/docs/webhooks/introduction), you can also build custom [dual-sided incentive structures](/help/article/dual-sided-incentives) – e.g. rewarding the referrer an additional 100 credits when a new user signs up with their link.

### 2. Influencer campaigns attribution tracking

You can also use [Dub Partners](https://dub.co/partners) to better [understand the performance of your influencer campaigns](/help/article/program-analytics) – not just the number of clicks, but also the number of signups/purchases.

You can then use the data to make data-driven decisions about which influencers to work with and how to better allocate your budget to maximize your ROI.

## Getting started with conversion tracking on Dub

To get started with conversion tracking on Dub, check out our [quickstart guide](/docs/quickstart/server).

<ImageLink alt="Conversion tracking quickstart guide" href="/docs/quickstart/server" />


# What is the Dub link builder?
Source: https://dub.co/help/article/dub-link-builder

Learn more about the Dub link builder, how to open it (with various shortcuts), and how to use it to create a short link.

Short links are the bread and butter of Dub. With Dub's link management platform, you can create short links and [QR codes](/help/article/custom-qr-codes) for your marketing campaigns and get [world-class analytics](/help/article/dub-analytics) on them.

At the center of Dub is the Dub link builder. This is where you'll find all the tools & features you need to create a short link on Dub.

<Frame>
  <img alt="The Dub link builder" />
</Frame>

## Features of the Dub link builder

On the left half of the link builder, Dub offers a variety of advanced link features that you can use to create and manage your short links.

1. **Tag selector**: Organize your links with tags to make them easy to find. [Learn more](/help/article/how-to-use-tags).
2. **Custom link previews**: Customize how your link appears on social media with the help of AI to improve conversion rates [Learn more](/help/article/custom-link-previews).
3. **UTM Builder**: Add UTM parameters to your links. [Learn more](/help/article/utm-builder).
4. **Link Cloaking**: Mask your destination URL with your short link. [Learn more](/help/article/link-cloaking).
5. **Password Protection**: Protect your links with a password. [Learn more](/help/article/password-protected-links).
6. **Expiration Date**: Set an expiration date for your links. [Learn more](/help/article/link-expiration).
7. **Device Targeting**: Set a custom destination URL for iOS and Android devices. [Learn more](/help/article/device-targeting).
8. **Geo Targeting**: Redirect your users to different links based on their location. [Learn more](/help/article/geo-targeting).
9. **Search Engine Indexing**: Control how your links are indexed by search engines. [Learn more](/help/article/how-noindex-works).
10. **Comments**: Add comments to your links – for you and your team. [Learn more](/help/article/link-comments).

On the right half of the link builder, you get a live preview of how your short link will look on social media platforms like **X (formerly Twitter)**, **Facebook**, and **LinkedIn**.

## Keyboard shortcuts in the Dub link builder

The Dub link builder also comes with a variety of keyboard shortcuts to help you create links faster. Here's a list of all the keyboard shortcuts you can use:

| Shortcut | Action                                                                 |
| -------- | ---------------------------------------------------------------------- |
| `D`      | Open the domain selector                                               |
| `T`      | Open the tag selector                                                  |
| `Q`      | Open the QR code modal                                                 |
| `L`      | Open the [link preview](/help/article/custom-link-previews) modal      |
| `U`      | Open the [UTM builder](/help/article/utm-builder)                      |
| `E`      | Open the [link expiration](/help/article/link-expiration) modal        |
| `G`      | Open the [geo/device targeting](/help/article/geo-targeting) modal     |
| `P`      | Open the [link password](/help/article/password-protected-links) modal |
| `K`      | Toggle [link cloaking](/help/article/link-cloaking)                    |
| `S`      | Toggle [search engine indexing](/help/article/how-noindex-works)       |
| `A`      | Open the advanced settings modal                                       |

## How to open the Dub link builder

There are 3 ways to open the Dub link builder:

* 🐢 **Slowest**: Click on the "Create link" button on the dashboard.
* 🐇 **Faster**: Use the keyboard shortcut `c` to open the link builder.
* 🐅 **Fastest**: Just paste in a valid URL into the dashboard. This will automatically open the link builder and pre-fill the URL field.

## Link creation shortcuts on Dub

We also have a 2 nifty shortcuts that you can use to open the link builder from anywhere on the internet:

1. Enter `dub.new` in your browser. This will open the link builder in your [default Dub workspace](/help/article/how-to-change-default-workspace).

<Frame>
  <video />
</Frame>

2. Prepend your short link domain in front of any URL. This will open the link builder in your [default Dub workspace](/help/article/how-to-change-default-workspace) with the URL and domain pre-filled.

<Frame>
  <video />
</Frame>


# Dub Links Overview
Source: https://dub.co/help/article/dub-links

Learn more about Dub Links, what it can do for your business and what makes it different to other solutions.

Dub Links is the modern link management platform for modern marketing teams.

With Dub, you can [create short links](/help/article/how-to-create-link) using your own [custom domain](/help/article/how-to-add-custom-domain) and get [world-class analytics](/help/article/dub-analytics) on your links' performance.

<Info>
  Fun fact: The name **Dub** comes from the literal definition of the word
  **"dub"** – which is to "give an unofficial name or nickname to something" (*a
  là what Dub Links does with longer URLs*).
</Info>

## What can I do with Dub Links?

If you are a marketer or a business owner, Dub has the perfect suite of features for you to better manage your marketing campaigns with short links:

<Steps>
  <Step title="Powerful link builder">
    <Frame>
      <img alt="The Dub link builder" />
    </Frame>

    Our [link builder](/help/article/dub-link-builder) comes with features like:

    * [UTM builder](/help/article/utm-builder)
    * [Custom link previews](/help/article/custom-link-previews)
    * [Device targeting](/help/article/device-targeting)
    * [Geo targeting](/help/article/geo-targeting)
    * [Password protection](/help/article/password-protected-links)
    * [Expiration dates](/help/article/link-expiration)
    * [Link cloaking](/help/article/link-cloaking)
  </Step>

  <Step title="Use your own domain">
    On Dub, you can create short links with your own [custom domain](/help/article/how-to-add-custom-domain) – for [better brand recognition](https://dub.co/blog/custom-domains).

    We also have one of the most generous custom domain limits in the industry – you can add up to 3 custom domains to your workspace for free, and even more with our [paid plans](https://dub.co/pricing).

    * [**Pro plan**](https://dub.co/pricing): 10 domains
    * [**Business plan**](https://dub.co/pricing): 40 domains
    * [**Enterprise plan**](https://dub.co/enterprise): Unlimited domains
  </Step>

  <Step title="Analytics that matter">
    We provide you with [powerful analytics](/help/article/dub-analytics) for your links, including geolocation, device, browser, and referrer information.

    <ImageLink alt="Dub's Analytics dashboard" href="https://d.to/stats/try" />

    Some of our analytics features include:

    * [Timeseries analytics chart](/help/article/dub-analytics#1-time-series-analytics-chart)
    * [Real-time events stream](/help/article/real-time-events-stream)
    * [Exporting your analytics data](/help/article/how-to-export-analytics)
    * ["Ask AI" feature](/help/article/dub-analytics#bonus-ask-ai-feature)
  </Step>

  <Step title="Free QR code generator">
    QR codes and short links are like peas in a pod. That's why we've built a [QR code generator](https://dub.co/tools/qr-code) right into Dub that lets you generate branded QR codes for your links.
  </Step>

  <Step title="Team collaboration">
    Dub is built for teams. You can [invite your teammates](/help/article/how-to-invite-teammates) to your account for free and manage your links together.

    On Dub [Enterprise](https://dub.co/enterprise), you can also set up [SAML/SSO and SCIM Directory Sync](/help/article/okta-saml) for greater security and control.
  </Step>

  <Step title="Enterprise-grade API">
    If you're planning to use Dub to create links programmatically, we have a powerful, [enterprise-grade API](/docs/api-reference/introduction) that allows you to do just that.

    We also offer native SDKs for:

    * [TypeScript](https://dub.co/solutions/typescript)
    * [Python](https://dub.co/solutions/python)
    * [Go](https://dub.co/solutions/go)
    * [Ruby](https://dub.co/solutions/ruby)

    Our API is used in production by engineering teams at companies like [Raycast](https://dub.co/customers/raycast), [Whop](https://whop.com), and [Midday](https://dub.co/customers/midday).
  </Step>
</Steps>

## Who uses Dub Links?

Dub Links is perfect for modern marketing teams that want a better link management platform for their marketing campaigns.

Here are some awesome companies that use Dub Links:

* Perplexity → [pplx.ai](https://pplx.ai)
* Vercel → [vercel.fyi](https://vercel.fyi)
* Raycast → [ray.so](https://ray.so)
* Clerk → [clerk.com](https://go.clerk.com)
* Framer → [framer.link](https://framer.link)
* Nuxt → [go.nuxt.com](https://go.nuxt.com)
* Prisma → [pris.ly](https://pris.ly)
* Tinybird → [tbrd.co](https://tbrd.co)
* Hashnode → [hshno.de](https://hshno.de)
* Cal.com → [go.cal.com](https://go.cal.com)
* Replicate → [replicate.fyi](https://replicate.fyi)
* Appwrite → [apwr.dev](https://apwr.dev)
* Attio → [attio.xyz](https://attio.xyz)
* Mux → [mux.link](https://mux.link)
* ETHGlobal → [ethglob.al](https://ethglob.al)

You can check out a full list of our customers on our [customers page](https://dub.co/customers).

## How is Dub Links different from other alternatives?

At Dub, we pride ourselves on having the best design & user experience out of all the link management platforms in the market. We've spent countless hours perfecting our product, and we're constantly improving it.

Our signature feature is the ability to [customize the social media cards](/help/article/custom-link-previews) for your links. This is a feature that no other link management platform has, and it's a game-changer for marketers who want to have full control over how their links look when shared on social media.

Dub also has the most generous free plan in the industry. With Dub's free tier, you can:

* [Create links](/help/article/how-to-create-link) with [custom domains](/help/article/how-to-add-custom-domain) (add up to 3 domains)
* Update your link destination however many times you want
* Get [advanced analytics](/help/article/dub-analytics) on your links's performance (including geolocation, device, browser, and referrer information)

Learn more about how we compare to other link management platforms:

* [Dub vs Bitly](https://dub.co/compare/bitly)
* [Dub vs Rebrandly](https://dub.co/compare/rebrandly)
* [Dub vs Short.io](https://dub.co/compare/short)

Start using Dub today by [creating a workspace](/help/article/what-is-a-workspace#how-to-create-a-workspace)!


# Why are my dub.sh links not working?
Source: https://dub.co/help/article/dub-links-not-working

Our default dub.sh links are occasionally blocked by certain web browsers and ISPs, which causes them not to work. Learn more about why, and how to fix it.

Occasionally, `dub.sh` short links are blocked by web browsers and Internet Service Providers (ISPs). When that happens, you might run into the following errors when you try to visit a `dub.sh` link:

## Error 1: "Your connection is not private" (Google Chrome)

Here's an example of a "Your connection is not private" warning in Google Chrome:

<Frame>
  <img alt="'Your connection is not private' warning" />
</Frame>

## Error 2: "This site has been reported as unsafe" (Microsoft Defender)

Here's another example of a "This site has been reported as unsafe" warning in Microsoft Defender:

<Frame>
  <img alt="Microsoft Defender's 'This site has been reported as unsafe' warning" />
</Frame>

## Error 3: "This site cannot provide a secure connection" (ERR\_SSL\_PROTOCOL\_ERROR)

And sometimes, certain Internet Service Providers (ISPs) will also block `dub.sh` links. This is what you might see when you try to visit a `dub.sh` link if it's blocked by your ISP:

<Frame>
  <img alt="'This site cannot provide a secure connection' error" />
</Frame>

## Why does this happen?

This is because `dub.sh` links are the default short link on Dub, and can sometimes be used by malicious actors to hide the true destination of a link – which can be a phishing site, or a site that downloads malware onto your computer.

As an unfortunate side effect, this means that sometimes, legitimate `dub.sh` links are also blocked by certain web browsers and ISPs, causing them not to work.

## Solution 1: Petition your ISP or browser antivirus software

One way to fix it is by sending a petition to your [Internet Service Provider (ISP)](https://support.google.com/googlenest/answer/6246513) or browser antivirus software to unblock the link. Here's an example verbiage you can use:

> Hi there,
>
> I would like to petition for the dub.sh domain to be unblocked. Dub ([https://dub.co](https://dub.co)) is a link management platform (similar to Bitly) that I use to share links with my friends and family. It is not a malicious site.
>
> Occasionally, there might be malicious links that are created on Dub, but Dub has processes in place that actively monitors and removes these links. I have also personally reported malicious links to Dub, and they have been removed within 24 hours.
>
> If you do discover any malicious links, you can report them here: [https://dub.co/legal/abuse](https://dub.co/legal/abuse)
>
> Thank you!

This is the most effective way to unblock the link, but it can take some time for the request to be processed.

## Solution 2: Use a custom domain

Alternatively, you can also [use Dub with your custom domain](/help/article/how-to-add-custom-domain), instead of the default `dub.sh` domain.

This will ensure that your links are never blocked by web browsers, and also give your links a more professional, branded look. Custom domains are also more trustworthy and can lead to **30% higher click-through rates**.

Learn more about [why you should use a custom domain](https://dub.co/blog/custom-domains) and how to [choose the right one](/help/article/choosing-a-custom-domain).

We also launched [1-year free .link domains on all paid plans](https://dub.co/blog/introducing-free-domains) recently that you can use for your short links:

<Frame>
  <img alt="Free .link domain" />
</Frame>


# Dub Partners Overview
Source: https://dub.co/help/article/dub-partners

Learn how you can use Dub to recruit, manage, and pay out your affiliates, publishers, influencers, UGC creators, user referrals, and more.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

In today's day and age, especially with the rise of AI, the best ideas and products are no longer sufficient to rise above the competition and become a category leader.

What works is having a powerful distribution network that can produce consistent, repeatable, and profitable results – ideally on autopilot.

A good example of this is a product-led partner program – think Dropbox's famous "Refer a friend" program, or Framer's beloved partner program that generates [close to \$1M in payouts](https://dub.co/customers/framer) every month.

**This is where [Dub Partners](https://dub.co/partners) comes in.**

<Frame>
  <img alt="Dub Partners" />
</Frame>

With Dub Partners, you can [recruit](/help/article/inviting-partners), [manage](/help/article/managing-program-partners), and [pay out](/help/article/partner-payouts) your partners – whether they're affiliates, publishers, influencers, UGC creators, user referrals, or more.

## Dub Partners platform tour

Here's a quick [9-min video](https://supercut.ai/share/dub/I85bqEKFm5aDaN_DC1hGoZ) that walks you through the key features of Dub Partners and how you can use it to scale your partner revenue:

<Frame>
  <div>
    <iframe title="Dub Partners Platform Tour" />
  </div>
</Frame>

## Dub Partners features

Here are some of Dub Partners' key features:

1. [Real-time analytics + reliable attribution](#real-time-reliable-attribution)
2. [Powerful reward structures (CPA/rev-share, CPL, CPC) with flexible conditions](#flexible-reward-structures)
3. [Social metrics bounties for influencer / UGC campaigns](#social-metrics-bounties)
4. [1-click global payouts + tax compliance](#global-payouts-%2B-tax-compliance)
5. [Risk monitoring (paid ads, self-referrals, etc.)](#risk-monitoring)
6. [Dual-sided incentives (via referral links / discount codes)](#dual-sided-incentives)
7. [Generate branded landing pages / application forms with AI](#landing-page-%2B-application-forms)
8. [Embed a referral dashboard directly within your app](#embedded-referral-dashboard)

### Real-time, reliable attribution

Built on top of Dub's [enterprise-grade attribution infrastructure](/docs/concepts/attribution), Dub Partners gives both you and your partners real-time visibility into the performance of your partner program, with the ability to:

* View [partner group](/help/article/partner-groups)-level analytics (conversion rates, revenue, etc.)
* Measure [individual partner performance](https://dub.co/changelog/new-partner-stats) (EPC, LTV, ROAS)
* Create custom reports with [advanced analytics filtering](/help/article/dub-analytics#advanced-analytics-filters)

<Frame>
  <img alt="Time-series chart" />
</Frame>

On top of that, Dub's [best-in-class Stripe integration](https://dub.co/integrations/stripe) (which you can [install in just a few clicks](/docs/integrations/stripe)) automatically tracks:

* sale (both one-time and recurring)
* [free trial](/docs/integrations/stripe#tracking-free-trials) lead events
* [refund events](/docs/conversions/sales/refunds)

...and reconciles them with your [partner commissions](/help/article/partner-commissions) – no manual calculations required.

### Flexible reward structures

With Dub Partners, you can create [flexible reward structures](/help/article/partner-rewards) to drive partner engagement and revenue:

| Reward type       | Description                                                                                                                                                                     |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CPA/rev-share** | This is the most common reward type, where you reward partners for driving sales to your product either via a rev-share or fixed-price commission.                              |
| **CPL**           | A more effective alternative to pay-per-click, where you reward partners for [qualified leads](/docs/conversions/leads/deferred) (e.g. demo requests, free trial signups, etc.) |
| **CPC**           | Rewarding partners for driving traffic to your product – similar to how Google Ads' pay-per-click works.                                                                        |

On top of that, you can also set geo-specific, product-specific, or subscription-specific [reward conditions](/help/article/partner-rewards#adding-reward-conditions):

* [Geo-specific rewards](/help/article/partner-rewards#geo-specific) (e.g. higher rewards for sales and leads from a specific country)
* [Product-specific rewards](/help/article/partner-rewards#product-spotlight) (e.g. higher rewards for sales of specific products)
* [Partner performance tiers](/help/article/partner-rewards#partner-performance-tiers) (e.g. higher rewards for partners with more than 100 conversions)
* [Staggered reward durations](/help/article/partner-rewards#staggered-reward-durations) (e.g. higher rewards for the first 12 months, then a lower reward for the rest of the customer's lifetime)
* [Sale amount modifiers](/help/article/partner-rewards#sale-amount-modifiers) (e.g. higher rewards for sales over a specific amount)

<Tip>
  Looking to set up non-monetary rewards for your partners (e.g. free credits,
  free months)? [Check out our guide here](/help/article/non-monetary-rewards).
</Tip>

### Social metrics bounties

One of Dub's unique features is the ability to [reward partners for creating viral social content](/help/article/program-bounties#social-metrics) about your product via a CPM model.

<Frame>
  <video />
</Frame>

Some examples include:

* Pay \$1,000 if your X/Twitter post gets 1M views
* Pay \$500 per 10K views on YouTube, up to 100K views

Aside from that, you can also reward partners a flat rate via [submission bounties](/help/article/program-bounties#manual-submission), as well as based on their performance on Dub via [performance bounties](/help/article/program-bounties#performance-bounties).

### Global payouts + tax compliance

Dub Partners supports [1-click payouts](/help/article/partner-payouts) to your partners worldwide.

If you're running a partner program with hundreds of partners, instead of manually sending payouts to each partner, you can use Dub to pay your partners in one click.

<Frame>
  <video />
</Frame>

We also provide invoices for each payout, so you can easily track your spend and reconcile your payments.

For US-based partners, Dub automatically handles [tax compliance](/help/article/partner-payouts#tax-compliance) by collecting W-9 forms and sending out 1099-NEC forms to individuals or entities that received \$600 or more in payments for a given fiscal year.

Here's the list of supported payout countries on Dub:

<PayoutSupportedCountries />

### Risk monitoring

As your program grows, it's critical to safeguard your revenue by detecting and preventing fraudulent activity. Dub comes with several risk monitoring mechanisms to help you catch and take action on any suspicious activity:

* [Paid traffic detection](/help/article/risk-monitoring#paid-traffic)
* Self-referrals / matching customer email
* Duplicate partner accounts / payout methods
* Network-level ban

Learn more about how [risk monitoring works](/help/article/risk-monitoring) on Dub.

### Dual-sided incentives

With Dub, you can create dual-sided incentives for your partners to give special discounts to their users – whether they're signing up via a [referral link](/help/article/dual-sided-incentives#option-1-direct-link-based-discounts-recommended) or a [discount code](/help/article/dual-sided-incentives#option-2-using-stripe-promo-codes-no-code-required).

<Frame>
  <video />
</Frame>

Some examples include:

* 25% discount for the first 12 months
* 30% lifetime discount
* \$50 one-off discount

This can drive powerful word-of-mouth growth as partners are more likely to share their link if it gives their audience additional discounts, and their users are more likely to click on their links if they're getting a special deal.

Learn more about [how to create dual-sided incentives](/help/article/dual-sided-incentives) for your partner program.

### Landing page + application forms

You can also use our intelligent AI landing page generator to create a compelling, [branded landing page](/help/article/program-landing-page) for your program to drive partner signups.

<Frame>
  <video />
</Frame>

You can also create customized [application forms](/help/article/program-application-form) for your partners to collect the information you need to review their applications.

### Embedded referral dashboard

One of the unique features of Dub is the ability to create an embedded referral dashboard directly inside your application.

<Frame>
  <video />
</Frame>

This allows you to seamlessly onboard your users to your referral program, without them needing to leave your app and sign up on a third-party platform.

[Read the guide](/docs/partners/embedded-referrals) on how to set this up, or check out this open-source example app that shows the embedded referral dashboard in action:

<ImageCtaCard alt="Dub embedded referral dashboard demo app" />

## Get started

With Dub, you can build a powerful, scalable word-of-mouth flywheel by incentivizing both your users and external partners to help you grow.

To get started with Dub, follow our [program quickstart](/help/article/setting-up-your-program) and [conversion tracking](/docs/quickstart/server) guides.

<CardGroup>
  <Card title="Setting up your program" icon="rocket" href="/help/article/setting-up-your-program">
    Step-by-step guide from zero to a live partner program
  </Card>

  <Card title="Conversion tracking" icon="arrow-progress" href="/docs/quickstart/server">
    Track conversions and attribute sign-ups and purchases
  </Card>
</CardGroup>

## Related articles

<CardGroup>
  <Card title="Partner tags" icon="tags" href="/help/article/partner-tags">
    Organize and filter partners with flexible internal labels
  </Card>

  <Card title="Partner groups" icon="users" href="/help/article/partner-groups">
    Segment partners by rewards, discounts, and performance
  </Card>

  <Card title="Inviting partners" icon="user-plus" href="/help/article/inviting-partners">
    Add partners via dashboard, API, or application form
  </Card>

  <Card title="Partner rewards" icon="money-bill" href="/help/article/partner-rewards">
    Create flexible reward structures for partners
  </Card>

  <Card title="Embedded referral dashboard" icon="grid-2" href="/docs/partners/embedded-referrals">
    Enroll partners directly from wihin your app
  </Card>
</CardGroup>


# Sending marketing and transactional emails to your partners
Source: https://dub.co/help/article/email-campaigns

Learn how to craft and send marketing and transactional emails to your partners to increase partner engagement and drive conversions.

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

On Dub, you can send marketing and transactional emails to your partners to boost partner engagement and increase conversions.

In this guide, we'll learn how to set up your email domain on Dub and start sending emails to your partners.

<Info>
  Sending partner emails with your own domain (e.g. `partners.acme.com`) is not
  only good for branding, but will also drastically improve deliverability and
  help avoid the dreaded spam folder.
</Info>

## Email domain setup

To send emails to your partners in Dub, you’ll need to connect an email domain that you own. We recommend using a subdomain (e.g. `partners.acme.com`) to improve trust and deliverability.

To set up your email domain, go to the "Email domains" tab in your workspace domain settings: [app.dub.co/settings/domains/email](https://app.dub.co/settings/domains/email)

<Frame>
  <img alt="Email domains" />
</Frame>

Click **Add domain**, and enter the domain you want to send emails from.

<Frame>
  <img alt="Add domain modal" />
</Frame>

You’ll receive DNS setup instructions and values to connect the domain. A **DMARC** record will also be provided — we strongly recommend adding it to help prevent email spoofing.

<Frame>
  <img alt="DNS records" />
</Frame>

While verifying your domain, you can still create and plan emails, but they won’t be sent until the domain is fully connected and active.

Once the connection is complete, the status will update to **Active**, and you’ll be ready to send partner emails.

<Frame>
  <img alt="Active email domain" />
</Frame>

### Warming up your email domain

“Warming up” your domain means gradually increasing your email-sending volume to build trust and improve deliverability. The goal is to send consistently without sudden spikes that might look suspicious to inbox providers.

Any time you start using a new domain or expect a large increase in sending volume, you should warm up your domain. A thoughtful warm-up plan helps prevent greylisting, throttling, and spam filtering, while building a solid sender reputation.

Keep an eye on your [bounce rate](#email-analytics), as it should stay below **4%**. If it rises above that, slow down your sending and review the cause.

Example warm-up plan:

1. Add your custom domain and start sending **transactional** emails to warm it up.
2. After 2–3 days, send a small **marketing** campaign to your smallest group (e.g., “VIP Group”), then a slightly larger one (“Silver Group”).
3. Once your domain is fully warmed up, you can send marketing campaigns regularly to all groups (e.g., “Default Group”).

## Where to find email campaigns

In your partner program's navigation, click **Email Campaigns** under the **Engagement** section.

<Frame>
  <img alt="Email location" />
</Frame>

## Creating a new campaign

Click **Create Campaign** in the top-right corner, then select the type of email you want to send.

<Frame>
  <img alt="Create campaign" />
</Frame>

### Marketing

Marketing emails are one-off messages you send manually. They're great for announcements, product updates, and promotions.

**Examples**:

* “Introducing our newest feature”
* “Black Friday / Cyber Monday promotion”
* “Check out our latest blog post or promo video”

### Transactional

Transactional emails are automated messages triggered by specific partner actions – like joining your program, reaching a milestone, or staying active for a certain period.

**Examples:**

* “Welcome to the program! Here’s how to start earning.”
* “Congrats on your first conversion — here’s how to reach your next 10.”
* “You’ve crossed \$10,000 in commissions! You’re now among our top earners.”

## Email builder

Both Marketing and Transactional emails include:

* **Name**: Internal name of your campaign
* **From**: The sender's name and email address. You can customize the username (the part before the `@`). Replies are sent to your program's [support email address](/help/article/program-resources#support-email).
* **To**: Choose specific partner groups or send to All Groups
* **Subject**: The subject line of the email
* **Preview**: The short text shown next to or below the subject line in the recipient’s inbox
* **Body:** The main content of your email — include text, images, or links

### Marketing fields

For your marketing campaigns, select the date and time for when you want the email to be sent. This is helpful when planning email campaigns for the future.

<Frame>
  <img alt="Marketing fields" />
</Frame>

### Transactional fields

Transactional campaigns include trigger settings instead of a date selector since they send automatically based on partner activity.

<Frame>
  <img alt="Transactional fields" />
</Frame>

| Logic               | Description                                                              |
| ------------------- | ------------------------------------------------------------------------ |
| Joins the program   | Sends an email when a partner enrolls in your program                    |
| Enrollment duration | Sends an email when a partner has been enrolled for a set number of days |
| Total leads         | Sends an email when a partner reaches a specific number of leads         |
| Total conversions   | Sends an email when a partner reaches a specific number of conversions   |
| Total revenue       | Sends an email when a partner earns a specific amount in revenue         |
| Total commissions   | Sends an email when a partner earns a specific amount in commissions     |

### Text content

The email builder supports a wide variety of styling and content, including markdown support. In the control area above the content, you can use these buttons to change the styling of the content or add partner variables using the `@` button.

<Frame>
  <img alt="Email text styles" />
</Frame>

As you're creating content, you can use markdown for the following content styles and types:

| Type              | Markdown                                     |
| ----------------- | -------------------------------------------- |
| Bold              | `**Bold text**`                              |
| Italic            | `*italic text*`                              |
| Strikethrough     | `~~Strikethrough text~~`                     |
| Inline Hyperlinks | `[Dub](https://dub.co)`                      |
| Ordered lists     | `1. First item` `2. Second item`             |
| Bulleted lists    | `- First item` `- Second item`               |
| Headings          | `# Heading level 1` and `## Heading level 2` |
| Partner variables | `@PartnerName` and `@PartnerEmail`           |

### Images

You can also enrich your emails with images by clicking the **Insert image** button in the content editor.

<Frame>
  <img alt="Email image button" />
</Frame>

Clicking will open your system file browser to select an image to use. It will be uploaded in the email where your cursor is located. You can also drag and drop images directly into the content area.

To add alternative text to images, hover over the added image and click the **Alt** button in the bottom-right corner. Then you can add a short description of the image for accessibility and SEO purposes.

<Frame>
  <img alt="Email alt text" />
</Frame>

## Email previews

Before launching a campaign, you can send a test version to yourself or teammates for review. Click the `⋮` in the top-right corner and select **Send Preview**. You can enter multiple email addresses separated by commas.

<Frame>
  <img alt="Preview email" />
</Frame>

## Email analytics

After sending a Marketing email or setting up a Transactional email, click the campaign to open its analytics view.

You’ll see key metrics like **delivered**, **bounced**, and **opened** rates, along with a list of which partners opened the email. This is helpful to measure engagement and performance.

<Frame>
  <img alt="Email metrics" />
</Frame>

## Partner replies

When a partner replies to any email you send through Dub, their message is always delivered to the support email address you set when creating your program. You can also view or update that address in your [resources page](/help/article/program-resources).

This means that even if you change your sending domain later, all partner replies will continue to arrive in the same inbox.


# How to filter analytics by folders
Source: https://dub.co/help/article/filter-analytics-by-folders

Learn how to create powerful campaign-specific reports for your links with our folders feature.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

On Dub, you can filter analytics by [folders](/help/article/link-folders). This allows you to create powerful campaign-specific reports for your links.

For example, you're using folders to group your affiliate links and SMS marketing links, and want to see how each of those campaigns are performing.

You can filter the analytics by each of those folders and get a real-time dashboard of their clicks and [conversion analytics](/help/article/dub-conversions).

***

Before you can filter analytics by folders, you need to [create a folder](/help/article/link-folders#creating-a-folder) and [add links to it](/help/article/link-folders#adding-links-to-a-folder).

Then, as your links start accruing clicks, you will be able to filter your analytics by folders.

<Steps>
  <Step title="Open Analytics filter dropdown">
    In the [Analytics tab](https://app.dub.co/analytics) of your Dub workspace, click on the **Filter** button to open the filter dropdown:

    <Frame>
      <img alt="Analytics filter dropdown folder" />
    </Frame>

    <Tip>
      You can also use the `F` keyboard shortcut to quickly open the filter dropdown.
    </Tip>
  </Step>

  <Step title="Select the 'Folder' filter option">
    Select the **Folder** option in the dropdown to open the folder filter:

    <Frame>
      <img alt="Analytics folder filter" />
    </Frame>
  </Step>

  <Step title="Click on the folder you want to filter by">
    Finally, click on the folder you want to filter by:

    <Frame>
      <img alt="Filtered analytics by folder" />
    </Frame>
  </Step>
</Steps>

## Viewing filtered analytics for a folder directly

Alternatively, you can also view the filtered analytics for a folder directly, simply by opening the `⋮` dropdown next to the folder in your links dashboard and select the "Analytics" option:

## Viewing folder analytics shortcut

<Frame>
  <img alt="View folder analytics shortcut" />
</Frame>

Voila! You have successfully filtered your analytics by folder. You can also [export your analytics data](/help/article/how-to-export-analytics) to a CSV file for further analysis.


# How to filter analytics by tags
Source: https://dub.co/help/article/filter-analytics-by-tags

Learn how to create powerful campaign-specific reports for your links with our tags feature.

On Dub, you can filter analytics by [tags](/help/article/how-to-use-tags). This allows you to create powerful campaign-specific reports for your links.

For example, you're running an Instagram influencer campaign with multiple links and you want to see how they're performing.

You can group the links with a **Instagram Influencers** tag and then filter the analytics by that tag to get a comprehensive report of how they're performing.

***

Before you can filter analytics by tags, you need to [create a tag](/help/article/how-to-use-tags#how-to-create-a-tag) and [assign it to the links you want to filter by](/help/article/how-to-use-tags#can-i-assign-multiple-tags-to-a-link).

Then, as your links start accruing clicks, you will be able to filter your analytics by tags.

<Steps>
  <Step title="Open Analytics filter dropdown">
    In the [Analytics tab](https://app.dub.co/analytics) of your Dub workspace, click on the **Filter** button to open the filter dropdown:

    <Frame>
      <img alt="Analytics filter dropdown" />
    </Frame>

    <Tip>
      You can also use the `F` keyboard shortcut to quickly open the filter dropdown.
    </Tip>
  </Step>

  <Step title="Select the 'Tags' filter option">
    Select the **Tags** option in the dropdown to open the tags filter:

    <Frame>
      <img alt="Analytics tags filter" />
    </Frame>
  </Step>

  <Step title="Click on the tag you want to filter by">
    Finally, click on the tag you want to filter by:

    <Frame>
      <img alt="Filtered analytics by tags" />
    </Frame>
  </Step>

  <Step title="Optional: Filter by multiple tags">
    You can also filter by multiple tags by selecting multiple tags in the dropdown.

    <Frame>
      <img alt="Filtered analytics by multiple tags" />
    </Frame>
  </Step>
</Steps>

Voila! You have successfully filtered your analytics by tags. You can also [export your analytics data](/help/article/how-to-export-analytics) to a CSV file for further analysis.


# How to filter by trigger type
Source: https://dub.co/help/article/filter-analytics-by-trigger

Learn how to filter between QR code scans, link clicks, and deep links in your Dub analytics.

On Dub, you can filter between QR code scans, link clicks, and [deep link open events](/docs/concepts/deep-links/quickstart) in your Dub analytics. This lets you understand how people are finding your links.

Here's how you can do that:

<Steps>
  <Step title="Go to your Analytics dashboard">
    Click on the **Analytics** page in the **Insights** group of your workspace short links.

    <Frame>
      <img alt="Analytics location in navigation" />
    </Frame>
  </Step>

  <Step title="Open Analytics filter dropdown">
    Then, click on the **Filter** button to open the filter dropdown:

    <Frame>
      <img alt="Analytics filter dropdown" />
    </Frame>
  </Step>

  <Step title="Select the 'Trigger' filter option">
    Click on **Trigger** to show available data options. If there are no results, filters won't appear.

    <Frame>
      <img alt="Analytics trigger filter" />
    </Frame>
  </Step>

  <Step title="Choose to filter by Link click or QR Scan">
    Lastly, click on the **Link click**, **QR Scan**, or **Deep Link** option to filter your data.

    <Frame>
      <img alt="Filtered analytics by QR code" />
    </Frame>
  </Step>

  Voila! You have successfully filtered your analytics by QR code scans, link clicks, or deep link open events!
</Steps>


# Filtering and visualizing your analytics by UTM parameters
Source: https://dub.co/help/article/filter-analytics-by-utms

Learn how to create powerful campaign-specific reports for your links by filtering your analytics by their respective UTM tags.

On Dub, you can filter your analytics by [UTM parameters](/help/article/utm-builder). This allows you to create powerful campaign-specific reports for your links.

For example, you're leveraging our [UTM templates feature](/help/article/how-to-create-utm-templates) to create standardized UTM parameters for your team to use in their marketing campaigns, you'd want a way to see how each of these UTMs are performing in your Analytics tab.

With our **UTM Parameters** tab, you can easily see how each of your UTM parameters are performing in real time.

<Frame>
  <img alt="" />
</Frame>

You can also filter your analytics by a specific UTM parameter that you'd like to create a custom report for:

<Steps>
  <Step title="Open Analytics filter dropdown">
    In the [Analytics tab](https://app.dub.co/analytics) of your Dub workspace, click on the **Filter** button to open the filter dropdown:

    <Frame>
      <img alt="Analytics filter dropdown" />
    </Frame>

    <Tip>
      You can also use the `F` keyboard shortcut to quickly open the filter dropdown.
    </Tip>
  </Step>

  <Step title="Select the 'UTM Source' filter option">
    Select the **UTM Source** option in the dropdown to open the tags filter:

    <Frame>
      <img alt="Analytics UTM source dropdown" />
    </Frame>

    If you're looking to filter by other UTM parameters, you can select them accordingly in the main filter dropdown.
  </Step>

  <Step title="Click on the UTM source you want to filter by">
    Finally, click on the UTM source you want to filter by:

    <Frame>
      <img alt="Filtered analytics by UTM source" />
    </Frame>
  </Step>
</Steps>


# Folders role-based access control (RBAC)
Source: https://dub.co/help/article/folders-rbac

Learn how to set role-based access control for your folders to limit access to links for select teammates.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing).
</Tip>

With folders, you can limit access to your links using fine-grained role-based access controls (RBAC).

E.g. if you have marketing teams from different departments within your Dub workspace, you can create folders for each team with role-based access controls to ensure members can only see the links that they should have access to.

<Frame>
  <img alt="Folders RBAC" />
</Frame>

In this guide, we will learn how to set role-based access control for your folders to limit access to links for select teammates.

## What is role-based access control (RBAC)?

Role-based access control (RBAC) is a security approach that restricts access to resources based on the roles of individual users. Think of it like a modern office building where different employees have access to different floors based on their job roles.

In Dub's link folders, RBAC lets you control who can access specific folders and what they can do with the links inside them. For example, marketing team members might only have access to the "Marketing Campaigns" folder, while team leads have full access to all folders.

This granular control ensures that sensitive links remain secure while still allowing teams to collaborate effectively within their designated areas.

## Workspace-level permissions

When you create a folder, you can decide what the workspace-level permissions are for the folder. Workspace owners have access and full permissions to every created folder.

<Frame>
  <img alt="Create new folder access" />
</Frame>

There are 3 options for the workspace-level permissions:

1. **Can edit** (default): Anyone in the workspace can access the folder and its links.
2. **Can view** (view-only): Workspace members can view the folder and its links, but not edit them.
3. **No access** (private folder): Only members added to the folder and workspace owners have access (via [folder-level permissions](#folder-level-permissions)).

<Tip>
  If you're on the [Pro plan](https://dub.co/pricing), you can only create
  folders with the default **"Can edit"** settings. Only [Business
  plan](https://dub.co/pricing) and above users can create folders with the
  **"Can view"** or **"No access"** settings.
</Tip>

## Folder-level permissions

You can also set folder-level permissions for each folder. This lets you set exactly which members can access the folder and what they can do with the links inside it.

To set folder-level permissions, open the `⋮` dropdown next to the folder in your links dashboard and select the "Members" option:

<Frame>
  <img alt="Folders dropdown" />
</Frame>

This will open up the member access control panel, where you can set different roles to different users:

<Frame>
  <img alt="Folders member option" />
</Frame>

| Role          | Description                                                                                                                   |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **Owner**     | Has full access to the folder and its links. <br /> Can also invite other members to the folder / manage their access levels. |
| **Editor**    | Can edit the folder and its links.                                                                                            |
| **Viewer**    | Can view the folder and its links.                                                                                            |
| **No access** | Cannot view the folder and its links.                                                                                         |

<Tip>
  If you are using [machine users](/docs/api-reference/tokens#machine-users) for
  your API/integrations, remember to assign them the correct folder permissions
  as well – they function the same way as regular users and would require the
  correct permissions to be able to perform actions within a folder.
</Tip>

## Requesting access to a folder

If you only have read access to a folder, you can request edit access by clicking on the **"Ask to edit"** button:

<Frame>
  <img alt="Request edit access folder" />
</Frame>

This will send an email to the folder admin, who can then grant edit/owner access to you to manage the folder and its links:

<Frame>
  <img alt="Folder access request email" />
</Frame>


# Free .link domain offer on Dub
Source: https://dub.co/help/article/free-dot-link-domain

Learn how to get a 1-year free .link domain on Dub to create short links that match your brand and improve click-through rates.

We're excited to share that we have partnered with [Nova Registry](https://nova.link/) to offer a **1-year free `.link` domain** to Dub users.

This is a great way to get started with [custom domains](https://dub.co/blog/custom-domains) and create short links that match your brand, which gives you [better brand recognition](https://dub.co/blog/custom-domains#better-brand-recognition) and improves click-through rates.

## How to get a free .link domain

To get a free `.link` domain, you need to have a paid plan on Dub ([Pro plan](https://dub.co/pricing) or above). Once you have a paid plan, you can claim your free `.link` domain by following these steps:

<Steps>
  <Step title="Go to your workspace domain settings">
    Go to your [workspace domain settings](https://app.dub.co/settings/domains). Here you'll see a banner prompting you to claim your free `.link` domain.

    <Frame>
      <img alt="Banner prompting you to claim your free .link domain" />
    </Frame>

    Alternatively, if you've dismissed the banner before, you can click on **Add Domain** and then click on the **Claim your free .link domain** button.

    <Frame>
      <img alt="Claim your free .link domain button" />
    </Frame>

    You can also claim your free `.link` domain directly in the onboarding flow:

    <Frame>
      <img alt="Claim your free .link domain" />
    </Frame>
  </Step>

  <Step title="Search for your .link domain">
    Next, search for the `.link` domain that you want to claim. If the domain is unavailable, you'll see a list of alternate domains that you can choose from:

    <Frame>
      <img alt="Search for your .link domain – domain is unavailable" />
    </Frame>

    If the domain is available, you'll see a **Claim domain** button. Click on the button to claim your free `.link` domain.

    <Frame>
      <img alt="Search for your .link domain – domain is available" />
    </Frame>
  </Step>

  <Step title="Claim your domain and wait for it to be provisioned">
    Once your domain is claimed, it'll show up on your domain dashboard with a **Provisioned by Dub** banner – as well as an amber **Provisioning** badge while your domain is being registered behind the scenes.

    <Frame>
      <img alt="Provisioning badge" />
    </Frame>

    Once the domain is provisioned, you'll see a green **Active** badge next to your domain. This means that your domain is ready to use.

    <Frame>
      <img alt="Active badge" />
    </Frame>

    <Info>
      The provisioning process can take anywhere between 1 hour to 12 hours. If your
      domain is not active after 12 hours, please [reach out to our support
      team](https://dub.co/contact/support).
    </Info>
  </Step>
</Steps>

## Terms and conditions

The free `.link` domain offer is subject to the following terms and conditions:

1. This offer is only available to users who have a paid plan on Dub.
2. Premium `.link` domains (also known as "[registry premium](https://www.dynadot.com/community/help/question/what-are-registry-premium-domains)" domains) are not eligible for this offer.
3. You can only claim one free `.link` domain per [workspace](/help/article/what-is-a-workspace).
4. The free `.link` domain offer lasts for exactly 1 year from the date of claim. A month before the end of the 1-year period, we'll send you an email prompting you to renew your domain – during which you can choose to pay the renewal fee to keep your domain, or let it expire.
5. After the free first year period, the renewal fee will be **\$12/year** (\$1/month billed yearly).
6. If you cancel your plan, your existing `.link` domain and links will still work, but you will no longer be able to create new links with it. In this case, we also reserve the right to reassign your `.link` domain to another paying Dub user.
7. We do not currently support transferring your free `.link` domain to another registrar at the moment, but we're planning to add the ability to do so after your first free year.


# Free 14-day trial
Source: https://dub.co/help/article/free-trial

Learn about the 14-day free trial on Dub, what limits apply during the trial, and what happens after it ends.

Dub offers a free trial so you can explore additional features before committing to a paid plan. The trial gives your workspace full access to paid plan features, subject to the [trial limits](#trial-limits) below.

<Note>
  To prevent abuse, we currently only offer one free trial per workspace.
</Note>

## Trial limits

To prevent abuse, we apply additional usage limits during your trial period for certain resources:

| Resource                                                                            | Trial limit |
| ----------------------------------------------------------------------------------- | ----------- |
| 👥 [**Dub Partners**](https://dub.co/partners)                                      |             |
| [Partners](/help/article/dub-partners)                                              | 50          |
| [Partner Groups](/help/article/partner-groups)                                      | 5           |
| [Payouts](/help/article/partner-payouts)                                            | \$500/mo    |
|                                                                                     |             |
| 🔗 [**Dub Links**](https://dub.co/links)                                            |             |
| [Links](/help/article/dub-links)                                                    | 100/mo      |
| [Analytics](/help/article/dub-analytics)                                            | 5,000/mo    |
| [Domains](/help/article/how-to-add-custom-domain)                                   | 5           |
| [Tags](/help/article/how-to-use-tags)                                               | 5           |
| [Folders](/help/article/link-folders)                                               | 5           |
|                                                                                     |             |
| 🏛️ **Other resources**                                                             |             |
| [Teammates](/help/article/how-to-invite-teammates)                                  | 3           |
| [API rate limit](/docs/api-reference/rate-limits)                                   | 120 req/min |
| [Analytics API rate limit](/docs/api-reference/rate-limits#rate-limits-by-endpoint) | 2 req/min   |

<Tip>
  To monitor your workspace's current usage, head to [**Settings →
  Billing**](https://app.dub.co/settings/billing) in your dashboard.
</Tip>

Once your usage reaches 100% of any of the limits above, you will need to upgrade to a paid plan to unlock full access to your plan's limits.

## Trial emails

When you start a free 14-day trial on Dub, we will send you the following emails:

* **Trial started**: The first email confirming your trial has started, along with a few resources to help you get started.
* **Mid-trial check-in**: A mid-trial check-in email to see how you're doing and if you need any help.
* **3 days before trial ends**: A final reminder email 3 days before your trial ends.

## What happens after the trial ends?

Once your trial ends, your card will be automatically charged for the first month of your paid plan.

If you don't want to proceed with the paid plan, you can cancel your subscription any time during your trial. See [canceling your subscription](/help/article/how-to-cancel-subscription) for more details.


# How to use geo targeting on Dub?
Source: https://dub.co/help/article/geo-targeting

Learn how to use geo targeting on Dub to redirect your users to different links based on their location.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

Geo Targeting is a powerful feature that lets you redirect your users to different links based on their location.

This is useful if you want to do location-based personalization. For example, you can redirect your UK users to a UK-specific landing page, with the rest of the world going to your global landing page.

To use this feature, click on the **Targeting** button in the link builder. This will open a new modal with geo targeting options.

You can also quickly access the geo targeting feature by using the keyboard shortcut `G` while in the link builder.

To set up a geo-targeted link, you'll need to provide the following information:

1. **Country**: The country that you want to target.
2. **URL**: The URL that you want to redirect your users to if they are from the country that you have selected.

<Frame>
  <img alt="Use Geo Targeting to redirect your users to different links based on their location" />
</Frame>

You can also add multiple geo-targeted URLs by clicking on the "Add location" button.

Once you have added the locations and URLs, click on the "Add targeting" button to create your geo-targeted link.

When your users click on the link, they will be redirected to the URL that you have specified based on their location.

## Priority when combining with other features

If you enable multiple targeting features on the same link, they are evaluated in the following order:

1. **[Device targeting](/help/article/device-targeting)** (iOS/Android) – checked first
2. **Geo targeting** – checked next if no device rule matches
3. **[A/B testing](/help/article/ab-testing)** – applied last, only to traffic not already redirected by device or geo targeting

This means device targeting takes priority over geo targeting. If a user matches a device targeting rule, that rule applies even if a geo targeting rule also exists for their location.

## Usage with the Dub API

You can also use the [Dub API](/docs/api-reference/introduction) to create geo-targeted links.

To do this, you will need to set the `geo` field in the [link creation request](/docs/api-reference/links/create). The `geo` field should be an object with a [valid country code](#list-of-valid-country-codes) as the key and the respective URL as the value.

```json theme={null}
{
  "url": "https://example.com",
  "geo": {
    "US": "https://example.com/us",
    "GB": "https://example.com/uk"
  }
}
```

## List of valid country codes

Here's a list of valid country codes that you can use with the Dub API:

| Country Code | Country Name                                 |
| ------------ | -------------------------------------------- |
| AF           | Afghanistan                                  |
| AL           | Albania                                      |
| DZ           | Algeria                                      |
| AS           | American Samoa                               |
| AD           | Andorra                                      |
| AO           | Angola                                       |
| AI           | Anguilla                                     |
| AQ           | Antarctica                                   |
| AG           | Antigua and Barbuda                          |
| AR           | Argentina                                    |
| AM           | Armenia                                      |
| AW           | Aruba                                        |
| AU           | Australia                                    |
| AT           | Austria                                      |
| AZ           | Azerbaijan                                   |
| BS           | Bahamas                                      |
| BH           | Bahrain                                      |
| BD           | Bangladesh                                   |
| BB           | Barbados                                     |
| BY           | Belarus                                      |
| BE           | Belgium                                      |
| BZ           | Belize                                       |
| BJ           | Benin                                        |
| BM           | Bermuda                                      |
| BT           | Bhutan                                       |
| BO           | Bolivia                                      |
| BA           | Bosnia and Herzegovina                       |
| BW           | Botswana                                     |
| BV           | Bouvet Island                                |
| BR           | Brazil                                       |
| IO           | British Indian Ocean Territory               |
| BN           | Brunei Darussalam                            |
| BG           | Bulgaria                                     |
| BF           | Burkina Faso                                 |
| BI           | Burundi                                      |
| KH           | Cambodia                                     |
| CM           | Cameroon                                     |
| CA           | Canada                                       |
| CV           | Cape Verde                                   |
| KY           | Cayman Islands                               |
| CF           | Central African Republic                     |
| TD           | Chad                                         |
| CL           | Chile                                        |
| CN           | China                                        |
| CX           | Christmas Island                             |
| CC           | Cocos (Keeling) Islands                      |
| CO           | Colombia                                     |
| KM           | Comoros                                      |
| CG           | Congo (Republic)                             |
| CD           | Congo (Democratic Republic)                  |
| CK           | Cook Islands                                 |
| CR           | Costa Rica                                   |
| CI           | Ivory Coast                                  |
| HR           | Croatia                                      |
| CU           | Cuba                                         |
| CY           | Cyprus                                       |
| CZ           | Czech Republic                               |
| DK           | Denmark                                      |
| DJ           | Djibouti                                     |
| DM           | Dominica                                     |
| DO           | Dominican Republic                           |
| EC           | Ecuador                                      |
| EG           | Egypt                                        |
| SV           | El Salvador                                  |
| GQ           | Equatorial Guinea                            |
| ER           | Eritrea                                      |
| EE           | Estonia                                      |
| ET           | Ethiopia                                     |
| FK           | Falkland Islands                             |
| FO           | Faroe Islands                                |
| FJ           | Fiji                                         |
| FI           | Finland                                      |
| FR           | France                                       |
| GF           | French Guiana                                |
| PF           | French Polynesia                             |
| TF           | French Southern Territories                  |
| GA           | Gabon                                        |
| GM           | Gambia                                       |
| GE           | Georgia                                      |
| DE           | Germany                                      |
| GH           | Ghana                                        |
| GI           | Gibraltar                                    |
| GR           | Greece                                       |
| GL           | Greenland                                    |
| GD           | Grenada                                      |
| GP           | Guadeloupe                                   |
| GU           | Guam                                         |
| GT           | Guatemala                                    |
| GN           | Guinea                                       |
| GW           | Guinea-Bissau                                |
| GY           | Guyana                                       |
| HT           | Haiti                                        |
| HM           | Heard Island and McDonald Islands            |
| VA           | Vatican City                                 |
| HN           | Honduras                                     |
| HK           | Hong Kong                                    |
| HU           | Hungary                                      |
| IS           | Iceland                                      |
| IN           | India                                        |
| ID           | Indonesia                                    |
| IR           | Iran                                         |
| IQ           | Iraq                                         |
| IE           | Ireland                                      |
| IL           | Israel                                       |
| IT           | Italy                                        |
| JM           | Jamaica                                      |
| JP           | Japan                                        |
| JO           | Jordan                                       |
| KZ           | Kazakhstan                                   |
| KE           | Kenya                                        |
| KI           | Kiribati                                     |
| KP           | North Korea                                  |
| KR           | South Korea                                  |
| KW           | Kuwait                                       |
| KG           | Kyrgyzstan                                   |
| LA           | Laos                                         |
| LV           | Latvia                                       |
| LB           | Lebanon                                      |
| LS           | Lesotho                                      |
| LR           | Liberia                                      |
| LY           | Libya                                        |
| LI           | Liechtenstein                                |
| LT           | Lithuania                                    |
| LU           | Luxembourg                                   |
| MO           | Macao                                        |
| MG           | Madagascar                                   |
| MW           | Malawi                                       |
| MY           | Malaysia                                     |
| MV           | Maldives                                     |
| ML           | Mali                                         |
| MT           | Malta                                        |
| MH           | Marshall Islands                             |
| MQ           | Martinique                                   |
| MR           | Mauritania                                   |
| MU           | Mauritius                                    |
| YT           | Mayotte                                      |
| MX           | Mexico                                       |
| FM           | Micronesia                                   |
| MD           | Moldova                                      |
| MC           | Monaco                                       |
| MN           | Mongolia                                     |
| MS           | Montserrat                                   |
| MA           | Morocco                                      |
| MZ           | Mozambique                                   |
| MM           | Myanmar                                      |
| NA           | Namibia                                      |
| NR           | Nauru                                        |
| NP           | Nepal                                        |
| NL           | Netherlands                                  |
| NC           | New Caledonia                                |
| NZ           | New Zealand                                  |
| NI           | Nicaragua                                    |
| NE           | Niger                                        |
| NG           | Nigeria                                      |
| NU           | Niue                                         |
| NF           | Norfolk Island                               |
| MK           | Macedonia                                    |
| MP           | Northern Mariana Islands                     |
| NO           | Norway                                       |
| OM           | Oman                                         |
| PK           | Pakistan                                     |
| PW           | Palau                                        |
| PS           | Palestine                                    |
| PA           | Panama                                       |
| PG           | Papua New Guinea                             |
| PY           | Paraguay                                     |
| PE           | Peru                                         |
| PH           | Philippines                                  |
| PN           | Pitcairn                                     |
| PL           | Poland                                       |
| PT           | Portugal                                     |
| PR           | Puerto Rico                                  |
| QA           | Qatar                                        |
| RE           | Reunion                                      |
| RO           | Romania                                      |
| RU           | Russia                                       |
| RW           | Rwanda                                       |
| SH           | Saint Helena                                 |
| KN           | Saint Kitts and Nevis                        |
| LC           | Saint Lucia                                  |
| PM           | Saint Pierre and Miquelon                    |
| VC           | Saint Vincent and the Grenadines             |
| WS           | Samoa                                        |
| SM           | San Marino                                   |
| ST           | Sao Tome and Principe                        |
| SA           | Saudi Arabia                                 |
| SN           | Senegal                                      |
| SC           | Seychelles                                   |
| SL           | Sierra Leone                                 |
| SG           | Singapore                                    |
| SK           | Slovakia                                     |
| SI           | Slovenia                                     |
| SB           | Solomon Islands                              |
| SO           | Somalia                                      |
| ZA           | South Africa                                 |
| GS           | South Georgia and the South Sandwich Islands |
| ES           | Spain                                        |
| LK           | Sri Lanka                                    |
| SD           | Sudan                                        |
| SR           | Suriname                                     |
| SJ           | Svalbard and Jan Mayen                       |
| SZ           | Eswatini                                     |
| SE           | Sweden                                       |
| CH           | Switzerland                                  |
| SY           | Syrian Arab Republic                         |
| TW           | Taiwan                                       |
| TJ           | Tajikistan                                   |
| TZ           | Tanzania                                     |
| TH           | Thailand                                     |
| TL           | Timor-Leste                                  |
| TG           | Togo                                         |
| TK           | Tokelau                                      |
| TO           | Tonga                                        |
| TT           | Trinidad and Tobago                          |
| TN           | Tunisia                                      |
| TR           | Turkey                                       |
| TM           | Turkmenistan                                 |
| TC           | Turks and Caicos Islands                     |
| TV           | Tuvalu                                       |
| UG           | Uganda                                       |
| UA           | Ukraine                                      |
| AE           | United Arab Emirates                         |
| GB           | United Kingdom                               |
| US           | United States                                |
| UM           | United States Minor Outlying Islands         |
| UY           | Uruguay                                      |
| UZ           | Uzbekistan                                   |
| VU           | Vanuatu                                      |
| VE           | Venezuela                                    |
| VN           | Vietnam                                      |
| VG           | Virgin Islands, British                      |
| VI           | Virgin Islands, U.S.                         |
| WF           | Wallis and Futuna                            |
| EH           | Western Sahara                               |
| YE           | Yemen                                        |
| ZM           | Zambia                                       |
| ZW           | Zimbabwe                                     |
| AX           | Åland Islands                                |
| BQ           | Bonaire, Sint Eustatius and Saba             |
| CW           | Curaçao                                      |
| GG           | Guernsey                                     |
| IM           | Isle of Man                                  |
| JE           | Jersey                                       |
| ME           | Montenegro                                   |
| BL           | Saint Barthélemy                             |
| MF           | Saint Martin (French part)                   |
| RS           | Serbia                                       |
| SX           | Sint Maarten (Dutch part)                    |
| SS           | South Sudan                                  |
| XK           | Kosovo                                       |


# Configuring SAML SSO with Google Workspace
Source: https://dub.co/help/article/google-saml

For Dub Enterprise users, you can securely manage your team's access to Dub using Google Workspace SAML SSO.

<Tip>
  This feature is only available on [Dub Enterprise](https://dub.co/enterprise).
</Tip>

For Dub Enterprise users, you can securely manage your team's access to Dub using [Google Workspace SAML SSO](https://cloud.google.com/architecture/identity/single-sign-on).

## Step 1: Create SAML Integration

In your [Google Admin](https://admin.google.com/) dashboard, click on **Apps** in the sidebar and select **Web and mobile apps** from the list.

<Image alt="Apps > Web and mobile apps on the Google Admin Dashboard" />

If you don't already have an existing SAML application, click on **Add app** and select **Add custom SAML app** from the dropdown menu.

<Frame>
  <img alt="Create App on the Google Admin Dashboard" />
</Frame>

Enter the **App name** for your application, and click **Continue**.

<Frame>
  <img alt="Configuring SAML app name on the Google Admin Dashboard" />
</Frame>

In the next screen, click **Download Metadata** to download the metadata XML file and click **Continue**. You'll need this file in Step 3.

<Frame>
  <img alt="Downloading SAML metadata on the Google Admin Dashboard" />
</Frame>

Copy the following values and paste them under **Service provider details**:

```text title="ACS URL" theme={null}
https://api.dub.co/auth/saml/callback
```

```text title="Entity ID" theme={null}
https://saml.dub.co
```

<Frame>
  <img alt="Service provider details on the Google Admin Dashboard" />
</Frame>

Click **Next** to proceed to the next step.

## Step 2: Configure Attribute Mapping

On the next screen, under the **Attributes** section, click **Add Mapping** and add the following attribute mappings:

| Google Directory attributes | App attributes |
| --------------------------- | -------------- |
| `Primary email`             | `email`        |
| `First name`                | `firstName`    |
| `Last name`                 | `lastName`     |

<Frame>
  <img alt="Attribute Mapping on the Google Admin Dashboard" />
</Frame>

Once that's done, click **Finish**.

On the next screen, click **User access** to configure the application to allow users to log in.

<Frame>
  <img alt="User access on the Google Admin Dashboard" />
</Frame>

Check the **ON for everyone** checkbox and click **Save**.

<Frame>
  <img alt="Turn on access for everyone on the Google Admin Dashboard" />
</Frame>

This will allow users in your Google Workspace to automatically sign in to Dub with Google SAML SSO.

You can now return to the Dub dashboard to complete the SAML SSO configuration.

## Step 3: Configure SAML SSO on Dub

In your workspace settings, click on **Security** in the **Workspace** group.

<Frame>
  <img alt="Directory Sync section on the Dub Dashboard" />
</Frame>

Under the **SAML Single Sign-On** section, click on **Configure**. This will open up the SAML SSO modal:

1. Select **Google** as the SAML provider.
2. Upload the metadata XML file you downloaded in Step 1.
3. Click **Save changes**.

<Frame>
  <img alt="Google SAML SSO Modal" />
</Frame>

That's it! You're all set – your team can now sign in to Dub using Google SAML SSO.


# How noindex works on Dub
Source: https://dub.co/help/article/how-noindex-works

Learn how Dub supports noindex response headers for short links with custom domains.

If you're familiar with SEO best practices, you might have heard of the `noindex` meta tag that tells search engines not to index a page.

This is particularly important if you're using Dub with a [custom domain](/help/article/how-to-add-custom-domain) – specifically a [subdomain](/help/article/how-to-add-custom-domain#step-2b-adding-a-subdomain) – for your short links, since having them indexed could potentially have a negative impact on your SEO.

To prevent this, Dub automatically serves short links with a `noindex` response header when you're using a custom domain. This tells search engines not to index your short links, keeping them out of search results.

```bash theme={null}
GET /noindex HTTP/2
Host: d.to
User-Agent: curl/8.6.0

HTTP/2 302
location: https://dub.co/help/article/how-noindex-works
X-Robots-Tag: noindex # [!code highlight]
X-Powered-By: Dub
```

<Info>
  If you're using Dub with a [default Dub-branded
  domain](/help/article/default-dub-domains) (e.g. `dub.sh`, `git.new`), the
  `noindex` response header is ***not applied by default***.
</Info>

## How to index your short links on Google

If you would like your short links to be indexed anyway, you can easily enable indexing by following these steps:

1. Go to your [Dub dashboard](https://d.to/register).
2. Click on the `⋮` button of the link that you want to enable indexing for, which will open the dropdown menu.
3. In the dropdown menu, click on the **Edit** button, which will open the [Dub link builder](/help/article/dub-link-builder).
4. Click on the `...` button, which will open the dropdown menu.
5. In the dropdown menu, click on the **Add Search Engine Indexing** button to enable indexing.

<Frame>
  <img alt="Enable search engine indexing for a Dub short link" />
</Frame>

And that's it! Your link will now be served without the `noindex` response header:

```bash theme={null}
GET /doindex HTTP/2
Host: d.to
User-Agent: curl/8.6.0

HTTP/2 302
location: https://dub.co/help/article/how-noindex-works
X-Powered-By: Dub
```

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>


# How to add a custom domain to Dub
Source: https://dub.co/help/article/how-to-add-custom-domain

Learn how to add a custom domain to your Dub workspace for free - no credit card required.

Whenever you're creating a short link for marketing purposes, it is [important to use a custom domain](https://dub.co/blog/custom-domains) for better brand recognition and click-through rates.

At Dub, we have one of the most generous custom domain limits in the industry – you can add up to 3 custom domains to your workspace for free, and even more with our [paid plans](https://dub.co/pricing).

* [**Pro plan**](https://dub.co/pricing): 10 domains
* [**Business plan**](https://dub.co/pricing): 100 domains
* [**Advanced plan**](https://dub.co/pricing): 250 domains
* [**Enterprise plan**](https://dub.co/enterprise): Unlimited domains

In this guide, we will learn how to add a custom domain to your Dub workspace.

<Info>
  Not sure which custom domain to use? Check out our guide on [choosing a custom
  domain](/help/article/choosing-a-custom-domain) for some tips.
</Info>

## Complimentary .link custom domain

If you're on a paid plan on Dub ([Pro plan](https://dub.co/pricing) and above), you can claim a [complimentary .link custom domain](/help/article/free-dot-link-domain) and use it as your short link domain – no DNS setup needed.

<Frame>
  <img alt="Free .link domain" />
</Frame>

Read our guide on [how to claim your free .link domain on Dub](/help/article/free-dot-link-domain).

## Step 1: Add a custom domain

Once you have a custom domain, you can add it to your Dub workspace by following these steps:

1. Navigate to your workspace's Domains page – you can do that by clicking on the **Domains** link in the menu bar at the top of your workspace dashboard.
2. Click on the **Add Domain** button.
3. Enter the domain you want to use and click **Add domain**.

<Frame>
  <img alt="The Add Domain modal in Dub" />
</Frame>

## Step 2: Configure your domain

After adding your domain, you'll be presented with a DNS record that you'll need to add to your domain's DNS settings.

<Frame>
  <img alt="Setting up DNS records for your domains on Dub" />
</Frame>

## Step 2a: Adding an apex domain

If you're adding an apex domain (e.g. `example.com`), you'll need to add an `A` record with the following values:

* **Name**: `@` (or leave it blank)
* **Value**: `76.76.21.21`
* **TTL**: `86400` (or the highest value possible)

## Step 2b: Adding a subdomain

If you're adding a subdomain (e.g. `go.example.com`), you'll need to add a `CNAME` record with the following values:

* **Name**: `go` (or whatever subdomain you want to use)
* **Value**: `cname.dub.co`
* **TTL**: `86400` (or the highest value possible)

Note that we do not support using `www.` as a subdomain, since the `www.` subdomain will always redirect to the apex domain instead.

We recommend using the following variations for your subdomains:

* `go.example.com`
* `try.example.com`
* `links.example.com`
* `l.example.com`

<Warning>
  If you're using Cloudflare as your DNS provider, you might need to configure
  some extra steps for it to work properly. Please refer to our guide on [using
  Cloudflare domains with Dub](/help/article/using-cloudflare-domains) for more
  information.
</Warning>

## Step 3 (optional): Verify domain ownership

<Info>
  This step is only applicable if you are adding a domain that is currently
  being used on [Vercel](https://vercel.com/).
</Info>

Since we use Vercel as the DNS provider for Dub, you'll need to verify that you own the domain before you can add it to your Dub workspace.

<Frame>
  <img alt="Verifying domain ownership on Dub" />
</Frame>

To do that, you'll need to add a TXT record with the following values:

* **Name**: `_vercel`
* **Value**: `vc-domain-verify=go.yourdomain.com...` (the value will be provided to you on the Dub dashboard)

<Warning>
  Warning: If you are using this domain for another site, setting this TXT
  record will transfer domain ownership away from that site and break it. Please
  exercise caution when setting this record.

  <>  </>

  For example: Make sure that the domain that is shown in the TXT verification
  value (e.g. `go.yourdomain.com`) is actually the domain you want to use as a
  custom domain on Dub – and ***not your production site***.
</Warning>

## How long do I have to wait for my domain to work?

Domain configuration can take anywhere between 1 to 24 hours to complete. If your domain still doesn't work after 24 hours, please [reach out to us](https://dub.co/contact/support) and we'll be happy to help you out.


# How to cancel my Dub subscription?
Source: https://dub.co/help/article/how-to-cancel-subscription

Learn how to cancel your Dub subscription, or downgrade to a lower plan if needed.

First of all, if you are planning to cancel your subscription, we're really sorry to see you go! We would appreciate any feedback on how we could improve our platform – feel free to [reach out to us](https://dub.co/contact/support) and we'll be happy to help you out.

## How can I cancel my Dub subscription?

You can cancel your Dub subscription by following the steps below:

1. From your workspace dashboard (e.g. `app.dub.co/your-workspace`), click on the **Settings** tab.
2. On the left sidebar, click on **Billing**.
3. Click on the **Cancel subscription** button to cancel your subscription.

<Frame>
  <img alt="Billing section on Dub" />
</Frame>

We really hope to see you back someday!

## How can I downgrade my Dub subscription?

If you would like to downgrade your subscription instead, please follow these steps:

1. From your workspace dashboard (e.g. `app.dub.co/your-workspace`), click on the **Settings** tab.
2. On the left sidebar, click on **Billing**.
3. Click on the **Manage plan** button to see all available plans on Dub.

<Frame>
  <img alt="Billing section on Dub" />
</Frame>

4. Select the plan that works best for you. You can also click on the **Adjust usage** button and choose your preferred link or event usage level.

<Frame>
  <img alt="'Manage plan' section on Dub" />
</Frame>

If you have any questions or need further assistance, please [contact our support team](https://dub.co/contact/support).


# How to change my Dub billing information?
Source: https://dub.co/help/article/how-to-change-billing-information

Learn how to add a new payment method, change your default payment method, remove a payment method, and change your billing information.

To manage your billing information on Dub, please follow the steps below:

1. Go to your workspace's billing settings page by following this link: [https://app.dub.co/settings/billing](https://app.dub.co/settings/billing)
2. Scroll down to the **Payment Method** section.
3. Here, you can add a new payment method or update your existing payment methods. You can also see which payment method is currently being used as the default payment method.

<Frame>
  <img alt="Manage payment methods" />
</Frame>

## Adding a new payment method

To add a new payment method, click on the **Add Payment Method** button.

<Frame>
  <img alt="Add payment method" />
</Frame>

This will redirect you to the Stripe payment method creation flow, where you can enter your payment method details and save it.

## Changing your default payment method

To change your default payment method, click on the payment method you want to set as the default and click on the **Set as default** button.

<Frame>
  <img alt="Set default payment method" />
</Frame>

This will set the selected payment method as your default payment method for your [subscription](https://app.dub.co/settings/billing/invoices?type=subscription) and [domain renewal](https://app.dub.co/settings/billing/invoices?type=domainRenewal) payments.

<Note>
  For [partner payouts](/help/article/partner-payouts), Dub will default to your
  [connected bank account](/help/article/how-to-set-up-bank-account) if it is
  configured (since it has [lower
  fees](/help/article/partner-payouts#payout-fees-and-timing)). If you prefer to
  have payouts default to your credit card, remove your connected bank account
  and set your credit card as the default payment method.
</Note>

## Removing a payment method

To remove a payment method, click on the payment method you want to remove and click on the **Remove** button.

<Frame>
  <img alt="Remove payment method" />
</Frame>

This will remove the selected payment method from your workspace. You can add it back again by [following the steps above](#adding-a-new-payment-method) to add a new payment method.

<Warning>
  You won't be able to remove your default payment method if it's the only
  payment method in your workspace. To remove your default payment method,
  you'll need to add a new payment method as the default first.
</Warning>

## Changing your billing information

If you want to change your billing information (e.g. company name, address, billing email, tax ID), you can do so by clicking on the **Open billing portal** button:

<Frame>
  <img alt="Open billing portal" />
</Frame>

This will redirect you to the Stripe billing portal, where you can change your billing information.


# How to change the default product in my workspace?
Source: https://dub.co/help/article/how-to-change-default-product

Learn how to set the default product tab that Dub shows when you open your workspace.

On Dub, you can choose which product tab ([Dub Partners](https://dub.co/partners) or [Dub Links](https://dub.co/links)) is shown by default when you open your [workspace](/help/article/what-is-a-workspace).

Here's how to change the default product in your workspace:

1. Go to your workspace settings by clicking on the gear icon in the sidebar (or visit `https://app.dub.co/<workspace-slug>/settings`).
2. Scroll down to the **Default Product** section.
3. Select the product you want to set as the default from the dropdown.

<Frame>
  <img alt="Selecting a default product in a Dub workspace" />
</Frame>

4. Click **Save changes** to apply your selection.

That's it! The next time you or a teammate opens this workspace, Dub will load the product tab you selected.

<Note>
  Only workspace owners and members with write access can change the default product. Read-only members will see the setting but can't update it.
</Note>


# How to change my default workspace on Dub?
Source: https://dub.co/help/article/how-to-change-default-workspace

Learn how you can set a workspace as your default workspace on Dub.

On Dub, you can set a [workspace of your choice](/help/article/what-is-a-workspace) as your default workspace. This is the workspace that you will be redirected to when you [log in to Dub](https://d.to/register).

When you create your first workspace on Dub, it will automatically be set as your default workspace. However, you can change your default workspace at any time.

Here's how you can change your default workspace on Dub:

1. Go to your [account settings](https://app.dub.co/account/settings) by clicking on your profile picture in the top right corner of the Dub dashboard and selecting **Settings**.
2. Scroll down to the **Your Default Workspace** section.
3. Select the workspace that you want to set as your default workspace from the dropdown list.

<Frame>
  <img alt="Selecting a default workspace in Dub" />
</Frame>

4. Click on the **Save Changes** button to save your selection.

That's it! Your default workspace has been updated. The next time you log in to Dub, you will be redirected to your chosen default workspace.


# How can I check a Dub link's destination before clicking on it?
Source: https://dub.co/help/article/how-to-check-link-destination

Learn how to check a Dub link's destination to make sure it's safe to click on.

If you're not sure where a Dub link will take you, you can check its destination before clicking on it by adding a `+` sign to the end of the short link.

For example, if you want to check the destination of the short link `d.to/github`, you can type `d.to/github+` into your browser's address bar and press `Enter`.

<Frame>
  <img alt="Inspect a Dub link's destination" />
</Frame>

This will take you to a page that shows you the destination URL of the short link, as well as the respective social media preview for the URL.

## Reporting a malicious link

If you suspect that a Dub link is malicious, you can report it by clicking on the `🚩` button on the link inspector page.

<Frame>
  <img alt="Reporting a malicious link on Dub" />
</Frame>

This will open up a form where you can provide more information about the malicious link.

<Frame>
  <img alt="Reporting a malicious link on Dub" />
</Frame>

We take these reports very seriously and will review and take appropriate action as soon as possible.

Learn more about the types of links that are against our [Terms of Service](https://dub.co/legal/terms#3-fair-use).


# How to create a short link on Dub?
Source: https://dub.co/help/article/how-to-create-link

Learn how to create your first short link on Dub and start tracking your links.

Dub's link builder comes with powerful features like [UTM builder](/help/article/utm-builder), [device targeting](/help/article/device-targeting), [password protection](/help/article/password-protected-links), [expiration dates](/help/article/link-expiration), and more.

Dub is also the only link management platform that lets you [customize the social media cards](/help/article/custom-link-previews) for your links, so you can have full control over how your links look when shared on social media.

In this article, we will take a look at how you can leverage these features to create your first short link on Dub.

## Step 1: Open the Dub link builder

There are 3 ways to open the [Dub link builder](/help/article/dub-link-builder) to create a short link:

1. **Slowest**: Click on the "Create link" button on the dashboard.
2. **Faster**: Use the keyboard shortcut `c` to open the link builder.
3. **Fastest**: Just paste in a valid URL into the dashboard. This will automatically open the link builder and pre-fill the URL field.

<Frame>
  <img alt="The Dub link builder" />
</Frame>

As you can see, the link builder also gives you a preview of your short link on social media platforms like X (formerly Twitter), Facebook, and LinkedIn.

## Step 2: Enter your destination URL

Enter your destination URL in the "Destination URL" field. You can enter the URL with or without the `https://` protocol – behind the scenes, Dub will automatically make sure it's formatted correctly.

## Step 3: Set a short link slug (optional)

Under the **Short Link** field, you can either:

1. Enter your own short link slug
2. Generate a random short link slug
3. [Generate a short link slug with AI](https://dub.co/blog/introducing-dub-ai)

<Frame>
  <video />
</Frame>

<Tip>
  This step is optional. If you don't enter a short link slug, Dub will generate
  one for you.
</Tip>

## Step 4: Customize your link previews (recommended)

You can also [set custom link previews](/help/article/custom-link-previews) for your short link to control how your links look when shared on social media platforms like X (formerly Twitter), LinkedIn, Slack, Telegram, iMessage, and more.

<Frame>
  <img alt="Create custom link previews for your links" />
</Frame>

You can also use AI to generate custom link previews for your links. [Learn more](/help/article/custom-link-previews).

<Frame>
  <video />
</Frame>

## Step 5: Add tags to your short link (optional)

Optionally, you can add tags to your short link to help organize your links and make them easier to find.

<Frame>
  <img alt="Fun fact: You can assign multiple tags to a given Dub link" />
</Frame>

You can also [filter your analytics by tags](/help/article/filter-analytics-by-tags), which allows you to create powerful campaign-specific reports for your links.

That's it! You've just created your first short link on Dub.

## Advanced link features

Additionally, Dub also offers a variety of advanced link features that you can use to enhance your short links:

1. **UTM Builder**: Add UTM parameters to your links. [Learn more](/help/article/utm-builder).
2. **Link Cloaking**: Mask your destination URL with your short link. [Learn more](/help/article/link-cloaking).
3. **Password Protection**: Protect your links with a password. [Learn more](/help/article/password-protected-links).
4. **Expiration Date**: Set an expiration date for your links. [Learn more](/help/article/link-expiration).
5. **Device Targeting**: Set a custom destination URL for iOS and Android devices. [Learn more](/help/article/device-targeting).
6. **Geo Targeting**: Redirect your users to different links based on their location. [Learn more](/help/article/geo-targeting).
7. **Search Engine Indexing**: Control how your links are indexed by search engines. [Learn more](/help/article/how-noindex-works).
8. **Comments**: Add comments to your links – for you and your team. [Learn more](/help/article/link-comments).

## Can I edit the destination URL of my link after it's created?

Yes, you can edit the destination URL of your link after it's created. To do so, click on the `⋮` button next to the link in the links list and select "Edit link".

<Frame>
  <img alt="Edit link popover" />
</Frame>

This will open the [individual link settings page](https://dub.co/changelog/link-pages), where you can edit the destination URL and save the changes.

Unlike other link platforms like [Bitly](https://dub.co/compare/bitly) where you need to [pay a whopping \$1](https://support.bitly.com/hc/en-us/articles/14354987499661-How-do-I-purchase-more-links-or-redirects) every time you want to update the destination URL of a short link, you can edit the destination URL of your link on Dub for free – for as many times as you want.


# How to create UTM templates on Dub
Source: https://dub.co/help/article/how-to-create-utm-templates

Learn how to create UTM templates on Dub to streamline UTM campaign management across your team.

UTM (short for *"Urchin Tracking Module"*) tags are one of the most common ways to track the performance of your marketing campaigns. They are a set of parameters that you can add to your links to track how users interact with your links.

Here's a list of the most common UTM parameters:

* `utm_source`: The source of the traffic, e.g. Google, Facebook, LinkedIn
* `utm_medium`: The medium of the traffic, e.g. cpc, organic, referral
* `utm_campaign`: The name of the campaign, e.g. summer\_sale, newsletter\_campaign
* `utm_term`: The term that triggered the traffic, e.g. "running shoes"
* `utm_content`: The specific content that triggered the traffic, e.g. "text\_link"

When working on a marketing team, it's important to be able to create standard UTM templates that can be reused across multiple links. This allows you to streamline your UTM campaign management and ensure consistency across your campaigns – which in turn improves the accuracy of your data.

<Tip>
  Learn more about the best practices when it comes to UTM tracking with our
  [Ultimate Guide to UTM Tracking](https://dub.co/blog/utm-guide).
</Tip>

On Dub, we support creating UTM templates that live on a workspace-level, allowing you to create a standardized set of UTM parameters that you can share with your team.

There are two ways to create UTM templates on Dub:

1. **Directly in the UTM Builder**
2. **In the UTM Templates Settings Page**

## Creating UTM Templates in the UTM Builder

To create a new UTM template in the [UTM Builder](/help/article/utm-builder), follow these steps:

<Steps>
  <Step title="Create a link">
    First, [create a link](/help/article/how-to-create-link) using the [Dub link builder](/help/article/dub-link-builder).

    <Frame>
      <img alt="The Dub link builder" />
    </Frame>
  </Step>

  <Step title="Open the UTM modal and add your UTM params">
    Click on the **UTM** button in the link builder. This will open the **UTM Builder** modal.

    You can also quickly access the **UTM Builder** feature by using the keyboard shortcut `U` while in the link builder.

    <Frame>
      <img alt="Use the UTM builder to populate UTM parameters for your links" />
    </Frame>

    Here, you can populate the UTM parameters for your link. Some example values include:

    * **UTM Source**: `google`
    * **UTM Medium**: `cpc`
    * **UTM Campaign**: `sale`
  </Step>

  <Step title="Save your UTM parameters as a new template">
    Then, click on the **Templates** button in the UTM Builder. This will open the UTM templates dropdown.

    You can then save your UTM parameters as a new template by entering a name for the template, and clicking **Enter**.

    <Frame>
      <img alt="The UTM templates dropdown" />
    </Frame>
  </Step>
</Steps>

## Creating UTM Templates in the UTM Templates Settings Page

To create a new UTM template in the UTM Templates Settings Page, follow these steps:

<Steps>
  <Step title="Go to the UTM Templates Settings Page">
    Go to the [UTM Templates Settings Page](https://app.dub.co/settings/library/utm) in your Dub workspace.

    <Frame>
      <img alt="The UTM templates settings page" />
    </Frame>
  </Step>

  <Step title="Open the UTM template editor and add your UTM params">
    Click on the **Create Template** button to open up the UTM template editor.

    <Frame>
      <img alt="The UTM template editor" />
    </Frame>

    Here, you can add your UTM parameters to the template.
  </Step>

  <Step title="Save your UTM template">
    Once you're done, click on the **Create template** button to save the template.

    This will create a new UTM template that you can use in the UTM Builder.

    <Frame>
      <img alt="A UTM template entry in the UTM templates list" />
    </Frame>
  </Step>
</Steps>

## Using UTM Templates

There are two ways to use a UTM template:

<Steps>
  <Step title="Method 1: Create a link and select the UTM template icon">
    First, [create a link](/help/article/how-to-create-link) using the [Dub link builder](/help/article/dub-link-builder).

    <Frame>
      <img alt="The Dub link builder" />
    </Frame>

    Then, click on the UTM icon right above the destination URL field. This will open up the list of UTM templates you have created in your workspace, which you can then select.

    <Frame>
      <img alt="UTM icon in the link builder" />
    </Frame>

    That's it! Your link will now have the UTM parameters from the template applied to it.
  </Step>

  <Step title="Method 2: Open the UTM modal and add select a UTM template">
    Alternative, you can also click on the **UTM** button in the link builder. This will open the **UTM Builder** modal.

    You can also quickly access the **UTM Builder** feature by using the keyboard shortcut `U` while in the link builder.

    <Frame>
      <img alt="Use the UTM builder to populate UTM parameters for your links" />
    </Frame>

    In the UTM Builder, click on the **Templates** button. This will open the UTM templates dropdown.

    You can then select your UTM template from the dropdown.

    <Frame>
      <img alt="Selecting a UTM template from the dropdown" />
    </Frame>
  </Step>
</Steps>


# How to delete my Dub account?
Source: https://dub.co/help/article/how-to-delete-account

Learn how to delete your account on Dub

First of all, if you're reading this article and are looking to delete your Dub account, we're sorry to see you go!

We would appreciate any feedback on how we could improve our platform – feel free to [reach out to us](https://dub.co/contact/support) to let us know.

You can delete your Dub account by following the steps below:

1. Navigate to your [account settings](https://app.dub.co/account/settings) on Dub.
2. Scroll down to the bottom of the Settings page, where you will find the **Delete Account** option.
3. Click on the **Delete Account** button. You will be prompted to enter the words **confirm delete account** in the input field.
4. After confirming, your account will be permanently deleted. Please note that this action is irreversible.

<Tip>
  **You cannot delete your account if you're an owner of a workspace.** You can
  either delete the workspace or transfer ownership to another user before
  deleting your account.
</Tip>

## How to delete my workspace?

You can delete your workspace by following these steps below:

1. Navigate to your workspace settings:

<Frame>
  <img alt="Workspace settings" />
</Frame>

2. Scroll down to the bottom of the Settings page, where you will find the
   Delete Workspace option. 3. Click on the Delete Workspace button. You will be
   prompted to enter the words confirm delete workspace in the input field.

<Tip>
  You can access your workspace settings by inserting your workspace slug into
  this link:
  [https://app.dub.co/Your-Workspace-Slug/settings](https://app.dub.co/Your-Workspace-Slug/settings)
</Tip>

If you have any questions or need further assistance, please [contact our support team](https://dub.co/contact/support).

We hope to see you back someday!

## When I delete my account, do you remove all my data?

Yes, when you delete your Dub account, all of your data will be permanently removed from our systems. This includes your Dub workspace, all of your data within the workspace, and any other data that is associated with your account.

Your email address will also be removed from our mailing list, and you will no longer receive any emails from Dub.


# How can I download my Dub invoice?
Source: https://dub.co/help/article/how-to-download-invoice

Learn how to download your Dub invoice.

First of all, thank you for upgrading to a paid plan on Dub! Your support means the world to us and helps us continue to build and improve Dub.

You can download your Dub subscription invoices from the [Invoices tab in your Dub workspace](https://app.dub.co/settings/billing/invoices):

<Frame>
  <img alt="Dub invoice download" />
</Frame>

Then, click on the "View invoice" to download the invoice you want.


# How to export link analytics data from Dub
Source: https://dub.co/help/article/how-to-export-analytics

Learn more about how to export link analytics data from Dub

Analytics can be powerful in understanding how your audience is interacting with your content and to make data-driven decisions.

For Dub, it's always been a top priority to provide [world-class analytics](/help/article/dub-analytics) to everyone.

<Frame>
  <img alt="CSV files are agnostic and can be opened in a variety of tools" />
</Frame>

To get more out of your analytics, you can export your analytics data to a CSV file. This can be useful if you want to analyze the data in a business intelligence (BI) tool, share it with your team, or import it into a spreadsheet.

## Exporting link analytics data

To export data from your link analytics, follow these steps:

<Steps>
  <Step title="Go to the analytics page">
    Go to your [Dub dashboard](https://d.to/register), and click **Analytics** from the **Insights** group of the workspace you want to export data from.

    <Frame>
      <img alt="Analytics page in the navigation" />
    </Frame>
  </Step>

  <Step title="Download the data">
    Click on the `⋮` button in the top right corner to open up the more options dropdown menu

    Click on the **Download as CSV** option.

    <Frame>
      <img alt="Click on the download icon to export the data" />
    </Frame>

    After a few seconds, the zip file will be downloaded to your computer. It contains a CSV file for every analytics type you already know from
    the dashboard (e.g. `countries.csv`, `devices.csv`, etc…).

    <Frame>
      <img alt="Find the CSV files in the downloaded zip file" />
    </Frame>
  </Step>
</Steps>


# How to export links from Dub
Source: https://dub.co/help/article/how-to-export-links

Learn more about how to export your links from your Dub workspace to a CSV file.

In your Dub workspace, you can export your links to a CSV file. This is useful if you want to analyze the data locally or share them with an external stakeholder.

To export your links, follow these steps:

<Steps>
  <Step title="Open the Export links modal">
    In your Dub workspace, navigate to the **Links** tab.

    Then, click on the `⋮` button to open the more options dropdown menu. Select the **Export links** option.

    <Frame>
      <img alt="Click on the more options dropdown menu to open the export links modal" />
    </Frame>
  </Step>

  <Step title="Select the date range">
    Choose the date range for the links you want to export. By default, Dub will export all links.

    <Frame>
      <img alt="Select the date range for the links you want to export" />
    </Frame>
  </Step>

  <Step title="Select the columns to export">
    Dub will export the following columns by default:

    * Short link
    * Destination URL
    * Clicks
    * Created at

    You can select the columns you want to export under the **Columns** section.

    <Frame>
      <img alt="Select the columns that you want to export" />
    </Frame>
  </Step>

  <Step title="Apply current filters">
    Optionally, you can choose to apply the current filters to the links you want to export. If you do, Dub will export the links that match the current filters; otherwise, it will export all links.

    <Frame>
      <img alt="Apply current filters to the links you want to export" />
    </Frame>
  </Step>

  <Step title="Download the data">
    Once you have selected the date range and columns to export, click on **Export links**.

    After a few seconds, the CSV file will be downloaded to your computer.
  </Step>
</Steps>


# How to import links from a CSV file
Source: https://dub.co/help/article/how-to-import-csv

Easily import your links from a CSV file into Dub.

You can also import your existing links into Dub via our CSV import feature. This is useful if you have a large list of links to import or if you want to [import links from another provider](/help/article/migrating-from-bitly) that we don't have a 1-click importer for.

To import your links, you can follow these steps:

<Steps>
  <Step title="Prepare your CSV file">
    Before you import your CSV file, make sure it is in the correct format. Your CSV file needs to have the following data:

    | Column          | Required | Description                                                    | Example                   |
    | --------------- | -------- | -------------------------------------------------------------- | ------------------------- |
    | Destination URL | **Yes**  | The destination URL of the link you want to import.            | `https://www.google​.com` |
    | Short link      | **Yes**  | The shortened version of the link.                             | `yourdomain.com/abc123`   |
    | Title           | No       | The title of the link.                                         | `Google`                  |
    | Description     | No       | The description of the link.                                   | `Search the web`          |
    | Tags            | No       | The tags of the link – must be a comma-separated list of tags. | `search, google, web`     |
    | Creation date   | No       | The date the link was created.                                 | `2024-01-01`              |

    The actual names of your columns can be anything you want, but you need to make sure that the required columns are present.
  </Step>

  <Step title="Upload your CSV file">
    If you haven't already, [create a workspace on Dub](/help/article/what-is-a-workspace).

    <Info>
      **Protip**: Once you create your workspace, we generally recommend you ***not
      to [configure your domain](/help/article/how-to-add-custom-domain)*** until
      you've migrated all your links. This will prevent any potential downtime for
      your links.
    </Info>

    Then, from your [links dashboard](https://d.to/register), click on the `⋮` button next to the **Create Link** button and select **Import from CSV**.

    <Frame>
      <img alt="Import from CSV button" />
    </Frame>

    This will open a modal with a file uploader.

    <Frame>
      <img alt="CSV import modal" />
    </Frame>

    Upload your CSV file, and you should be automatically redirected to the next step.
  </Step>

  <Step title="Review the AI-generated column mappings">
    When you upload a CSV file, [Dub AI](https://dub.co/blog/introducing-dub-ai) will automatically map the columns in your CSV file to the [supported data fields in Dub](/docs/data-model#links).

    <Frame>
      <img alt="CSV import column mapping" />
    </Frame>

    You can then review and edit the mappings before selecting **Confirm import**. In just a few seconds, all your links will be migrated to Dub.

    Alternatively, you can also select **Choose another file** to upload a different CSV file.
  </Step>

  <Step title="Review the import summary">
    Once all your links have been imported, we will send you an email to let you know that the migration is complete.

    <Frame>
      <img alt="Email notification about completed CSV import" />
    </Frame>

    If there are any issues with the import, we will send you a separate email with the details of the errors.

    <Frame>
      <img alt="Email notification about CSV import errors" />
    </Frame>
  </Step>

  <Step title="Configure your domain">
    Once all your links are imported, you can [configure your domain](/help/article/how-to-add-custom-domain#step-2-configure-your-domain) by setting the right DNS records. Once this is done, you can start using your links!
  </Step>
</Steps>


# How to invite teammates on Dub
Source: https://dub.co/help/article/how-to-invite-teammates

Learn how to invite teammates to your Dub workspace and start collaborating with your team.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

On Dub, you can easily invite your teammates to join your Dub workspace. This is useful if you want to collaborate with your team on your short links.

To start inviting your teammates, you first need to [create a workspace](/help/article/what-is-a-workspace#how-to-create-a-workspace) on Dub.

Once you have created a workspace, you have two different ways to invite your teammates:

* Via [email](#sending-an-invite)
* Via [invite links](#using-invite-links)

## Sending an invite

To invite a teammate via email, first navigate to **Members** in your workspace settings.

<Frame>
  <img alt="Workspace settings navigation" />
</Frame>

Click on the **Invite member** button to open the Invite Teammate modal.

<Frame>
  <img alt="Invite member button in workspace members settings" />
</Frame>

Enter your teammate's email address and [assign them a role](/help/article/workspace-roles).

You can also click on the **Add email** button to invite multiple teammates at once. Then, click on the **Send invite** button to send the invitations to your teammates.

<Frame>
  <img alt="Invite member modal" />
</Frame>

Your teammates will receive an invitation to join your Dub workspace via email:

<Frame>
  <img alt="Email invitation to join a Dub workspace" />
</Frame>

<Info>
  Invitations will be valid for 14 days – after which you will need to send a
  new one.
</Info>

## Using invite links

You can also invite your teammates to join your workspace by sharing an invite link with them.

First navigate to **Members** in your workspace settings. From here, click on the **Link** button in the top-right to open the Invite Link modal.

<Frame>
  <img alt="'Invite link' button in Dub" />
</Frame>

Click on the **Copy** button to copy the invite link to your clipboard.

<Frame>
  <img alt="The Invite link modal in Dub" />
</Frame>

```text title="Example of an invite link" theme={null}
https://app.dub.co/invites/xxxxxx
```

You can then share the invite link with your teammates via email, chat, or any other communication tool.

## Revoking / resending an invite

If you want to revoke/resend an invitation, you can do so by following these steps:

1. Navigate to your workspace's Members Settings page.
2. Click on the **Invitations** tab.
3. Click on the `⋮` button next to the invitation you want to revoke.
4. Click on the **Remove** button to open the Remove Invitation modal.
5. Click on the **Confirm remove** button to confirm.

<Frame>
  <img alt="The Remove Invitation modal in Dub" />
</Frame>

## Removing a teammate

If a teammate no longer needs access to your workspace, you can remove them from your workspace.

1. Navigate to your workspace's Members Settings page.
2. Click on the `⋮` button next to the teammate you want to remove.
3. Click on the **Remove** button to open the Remove Teammate modal.
4. Click on the **Confirm remove** button to confirm.

<Frame>
  <img alt="The Remove Teammate modal in Dub" />
</Frame>

In some cases, a user might also be associated with [created API
keys](/docs/api-reference/tokens), so you'll see a slightly different "remove
modal", showing the keys this user is associated with. If you delete a user,
their corresponding API keys will be deleted as well.

<Frame>
  <img alt="The Remove Teammate modal in Dub" />
</Frame>

## How many teammates can I invite?

The number of teammates you can invite depends on your Dub plan:

* **Free plan**: You cannot invite any teammates.
* **Pro plan**: You can invite up to 3 teammates.
* **Business plan**: You can invite up to 10 teammates.
* **Advanced plan:** You can invite up to 20 teammates.
* **Enterprise plan**: You can invite unlimited teammates.

For a complete comparison of features, [view all our plans available](https://dub.co/pricing).


# How to set up a redirect for your custom domain on Dub
Source: https://dub.co/help/article/how-to-redirect-root-domain

Learn how to redirect your custom domain to a URL of your choice on Dub.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

When you add a custom domain to your workspace, Dub will automatically create a placeholder page for your domain. This page will be displayed when you visit your domain.

<Frame>
  <img alt="Placeholder page for a custom domain on Dub" />
</Frame>

To redirect your domain to a URL of your choice, follow these steps:

1. If you haven't already, upgrade to the [Pro plan](https://dub.co/pricing).
2. Navigate to your workspace's Domains page – you can do that by clicking on the **Domains** link in the menu bar at the top of your workspace dashboard.
3. Click on the `⋮` button next to the domain you want to redirect, which will open a dropdown menu.

<Frame>
  <img alt="More options popover" />
</Frame>

4. In the dropdown menu, click on the **Edit Link** button, which will open the [Dub link builder](/help/article/dub-link-builder) for the root domain link.
5. Enter the URL you want to redirect your domain to in the **Destination URL** field.

<Frame>
  <img alt="The 'Domain Settings' modal in Dub" />
</Frame>

6. Click "Save changes". Your root domain link will now redirect to the URL you just specified.

<Note>
  If you don't have a destination URL configured on your root domain link, clicks on your root domain link won't count towards your [monthly clicks limit](/help/article/dub-analytics-limits).
</Note>

## Advanced link features

Additionally, Dub also offers a variety of advanced link features that you can use to enhance your root domain links:

1. **Custom Link Previews**: Customize how your links show up on social media to improve click-through rates. [Learn more](/help/article/custom-link-previews).
2. **Tags**: Assign tags to your links to organize them. [Learn more](/help/article/how-to-use-tags).
3. **UTM Builder**: Add UTM parameters to your links. [Learn more](/help/article/utm-builder).
4. **Link Cloaking**: Mask your destination URL with your short link. [Learn more](/help/article/link-cloaking).
5. **Password Protection**: Protect your links with a password. [Learn more](/help/article/password-protected-links).
6. **Expiration Date**: Set an expiration date for your links. [Learn more](/help/article/link-expiration).
7. **Device Targeting**: Set a custom destination URL for iOS and Android devices. [Learn more](/help/article/device-targeting).
8. **Geo Targeting**: Redirect your users to different links based on their location. [Learn more](/help/article/geo-targeting).
9. **Search Engine Indexing**: Control how your links are indexed by search engines. [Learn more](/help/article/how-noindex-works).
10. **Comments**: Add comments to your links – for you and your team. [Learn more](/help/article/link-comments).


# How to set a primary domain on Dub?
Source: https://dub.co/help/article/how-to-set-primary-domain

Learn what a primary domain is on Dub and how to set it up.

Dub is the only link management platform that allows you to [add custom domains](/help/article/how-to-add-custom-domain) for free. On Dub, you can add up to 3 domains on the free plan, and 10+ domains on the [Pro plan](https://dub.co/pricing) and above.

When you add multiple domains to your Dub account, the first domain you add will automatically be set as the primary domain. This domain will be the default domain in the Dub link builder:

<Frame>
  <img alt="Primary domain in the Dub link builder" />
</Frame>

It will also be the default domain if when using the [Dub API](/docs/api-reference/introduction). Here's an example of how it works with our [TypeScript SDK](https://d.to/sdk):

```typescript title="config.js" theme={null}
import { Dub } from "dub";

const dub = new Dub();

async function main() {
  const link = await dub.links.create({
    url: "string",
    domain: "dub.sh", // (optional - defaults to primary domain) // [!code highlight]
  });

  console.log(link);
}

main();
```

If you want, you can change the primary domain to another domain that you have added to your Dub account.

Here's how you can set a primary domain on Dub:

1. Go to the [Dub dashboard](https://d.to/register).
2. Click on the **Domains** tab on the top navigation bar.
3. Click on the `⋮` button of domain that you want to set as the primary domain, which will open a dropdown menu.
4. In the dropdown menu, click on the **Set as Primary** button, which will open the primary domain modal.

<Frame>
  <img alt="More options popover" />
</Frame>

5. Click on the **Set as primary domain** button in the modal.

<Frame>
  <img alt="Set as primary domain modal" />
</Frame>

Voilà! You have successfully set a primary domain on Dub. Now, the domain you have set as the primary domain will be the default domain in your Dub link builder and when using the [Dub API](/docs/api-reference/introduction).


# Setting up a bank account for partner payouts
Source: https://dub.co/help/article/how-to-set-up-bank-account

Start paying out your program partners using your bank account (direct debit via ACH, SEPA, or PADS).

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

With [Dub Partners](/help/article/dub-partners), you can [send payouts](/help/article/partner-payouts) to your affiliates using your credit card on file. However, for larger payouts, it might be more cost-efficient to set up your bank account for direct debit instead.

Depending on your bank's country, we support 3 different types of direct debit methods:

* **US**: Automated Clearing House (ACH)
* **Canada**: Automated Clearing Settlement System (ACSS)
* **Europe**: Single Euro Payments Area (SEPA)

<Info>
  Due to the fraud/dispute risks involved with SEPA direct debit, it is
  currently only available on [Enterprise](https://dub.co/enterprise) plans.
</Info>

## Connecting your bank account to Dub

Here's how you can connect your bank account to Dub for partner payouts:

<Steps>
  <Step title="Add a new direct debit method">
    On your [workspace's billing tab](https://app.dub.co/settings/billing), scroll down to **Payment methods** section.

    Click on the **Connect** button to add a new direct debit method:

    <Frame>
      <img alt="" />
    </Frame>

    This will open up a modal with the following direct debit methods:

    * **US**: Automated Clearing House (ACH)
    * **Canada**: Automated Clearing Settlement System (ACSS)
    * **Europe**: Single Euro Payments Area (SEPA)

    <Frame>
      <img alt="" />
    </Frame>

    Select the correct method based on your bank's location, and you'll be redirected to the Stripe connection flow.
  </Step>

  <Step title="Connect your bank account">
    Next, search for your bank that you want to connect to Dub:

    <Frame>
      <img alt="" />
    </Frame>

    Search for your bank, and follow the steps to connect your account.

    <Frame>
      <img alt="" />
    </Frame>

    If you selected SEPA direct debit in the previous step, you will need to manually enter your IBAN number and account details to set it up instead:

    <Frame>
      <img alt="" />
    </Frame>
  </Step>

  <Step title="Manually verify bank account">
    Alternatively, you can also manually verify your bank account via the [micro-deposits verification method](https://stripe.com/ie/resources/more/what-is-micro-deposit-verification-here-is-how-it-works).

    To do that, select the **Enter bank details manually** option:

    <Frame>
      <img alt="" />
    </Frame>

    Then, enter your bank details (routing number, account number):

    <Frame>
      <img alt="" />
    </Frame>

    You'll receive a micro deposit in your account within 1-2 business days that will include a six-character descriptor code starting with `SM`:

    ```
    SMXXXX
    ```

    Once you receive the micro-deposit, [reach out to us](https://dub.co/contact/support) with the code and we'll help you verify your bank account.
  </Step>
</Steps>

## My bank account is currently unavailable

Depending on which bank you select, Stripe might show an error that the bank is currently unavailable:

<Frame>
  <img alt="" />
</Frame>

If that happens, follow step 3 above to manually verify your bank account.


# How to transfer domains between workspaces?
Source: https://dub.co/help/article/how-to-transfer-domains

Learn how to transfer your custom domains and their associated links from one Dub workspace to another.

<Info>
  This guide is for transferring custom domains between Dub workspaces. If you
  want to transfer [default Dub domains](/help/article/default-dub-domains)
  between workspaces, please refer to the guide on [how to transfer links
  between workspaces](/help/article/how-to-transfer-links).
</Info>

Transferring domains between workspaces is a common use case for many Dub users. Some examples:

* You're working with a client on a project and want to transfer the custom domains you've set up for them to their workspace
* You're [selling your custom link shortener](https://dub.co/customers/pallyy) and want to transfer the custom domain to the new owner's workspace

This guide will show you how to transfer your Dub custom domains from one workspace to another – in just a few clicks!

<Warning>
  When transferring a custom domain between workspaces, all links associated
  with the domain will also be transferred as well.

  Transferring a domain will also fully reset the stats for the domain and its
  associated links and is irreversible – proceed with caution.
</Warning>

## Step 1: Open the domain transfer modal

For a given domain, click on the `⋮` button and select **Transfer**.

<Frame>
  <img alt="Transfer domain option" />
</Frame>

## Step 2: Select the destination workspace

In the domain transfer modal, select the workspace you want to transfer the link to:

<Frame>
  <img alt="Transfer domain modal" />
</Frame>

Click on **Confirm transfer** to complete the transfer.

That's it! Your domain and its associated links should now be successfully transferred to the new workspace.

<Warning>
  Transferring a domain will also fully reset the stats for the domain and its
  associated links and is irreversible – proceed with caution.
</Warning>

## Caveats

* Transferring a domain will also **fully reset the stats** for the domain and its associated links and is irreversible – please proceed with caution.
* You cannot transfer a [primary domain](/help/article/how-to-set-primary-domain) to another workspace. You'll need to set another domain as primary before transferring the existing primary domain.
* Link tags are not transferred when you transfer a domain and its links between workspaces. You'll need to re-apply the tags to the link in the new workspace.
* You can only transfer links to workspaces that you have access to. If you don't have access to the destination workspace, you won't be able to transfer the domain.


# How to transfer links between workspaces?
Source: https://dub.co/help/article/how-to-transfer-links

Learn how to transfer your Dub default short links from one workspace to another.

Transferring links between workspaces is a common use case for many Dub users, especially when they want to reorganize their links or consolidate them into a single workspace.

This guide will show you how to transfer your Dub short links from one workspace to another.

<Warning>
  Transferring a link will also **fully reset its stats** and is irreversible
  – please proceed with caution.
</Warning>

## Step 1: Open the link transfer modal

For a given link, click on the `⋮` button and select **Transfer**.

<Frame>
  <img alt="Transfer link option" />
</Frame>

You can also use the `T` keyboard shortcut to open the transfer modal.

## Step 2: Select the destination workspace

In the transfer modal, select the workspace you want to transfer the link to:

<Frame>
  <img alt="Transfer link modal" />
</Frame>

Click on **Confirm transfer** to complete the transfer.

That's it! Your link should now be successfully transferred to the new workspace.

<Warning>
  Transferring a link will also **fully reset its stats** and is irreversible
  – please proceed with caution.
</Warning>

## Caveats

* Transferring a link will also **fully reset its stats** and is irreversible – please proceed with caution.
* You can only transfer [default Dub domains](/help/article/default-dub-domains) (e.g. \`dub.sh\`, \`chatg.pt\`, \`spti.fi\`, \`amzn.id\`, etc.) between workspaces. If you want to transfer a custom domain link, you'll need to [transfer the domain](/help/article/how-to-transfer-domains) itself to the new workspace.
* Link tags are not transferred when you transfer a link between workspaces. You'll need to re-apply the tags to the link in the new workspace.
* You can only transfer links to workspaces that you have access to. If you don't have access to the destination workspace, you won't be able to transfer the link.


# How to use Dub to shorten links on a subpath
Source: https://dub.co/help/article/how-to-use-dub-with-subpath

Learn how to configure Dub to handle redirects on a subpath of an existing domain.

With Dub, you can easily set up redirects on a subpath of an existing domain.

This is useful if you are already using the domain for your website/app and only want to use Dub for a specific subpath.

For example, if you have a website at `acme.com` and want to add link-sharing features under `acme.com/r/` (e.g. `acme.com/r/123`), you can use Dub to handle the redirects for this subpath without affecting the rest of your website.

## Step 1: Add a subdomain to Dub

First, you need to add a subdomain of your existing domain to Dub.

For example, if your domain is `acme.com`, you can add a subdomain like `r.acme.com`.

You can follow [these steps](/help/article/how-to-add-custom-domain#step-2b-adding-a-subdomain) to add a subdomain to Dub.

## Step 2: Configure rewrites/redirects

Once you've added the subdomain, you can use rewrites or redirects to mask the subdomain and handle the redirects on the subpath.

<Frame>
  <img alt="URL Rewrites example" />
</Frame>

There are a few ways to configure this, depending on your application framework/hosting provider.

## Using Next.js

If you are using [Next.js](https://nextjs.org/), you can use the [`rewrites` property](https://nextjs.org/docs/app/api-reference/next-config-js/rewrites) in your `next.config.js` file to configure subpath redirects.

```javascript title="next.config.js" theme={null}
module.exports = {
  async rewrites() {
    return [
      {
        source: "/r/:path*",
        destination: "https://r.acme.com/:path*",
      },
    ];
  },
};
```

## Using Nuxt

If you are using [Nuxt](https://nuxt.com/), you can use Nuxt's [server route](https://nuxt.com/docs/guide/directory-structure/server#server-routes) feature to configure subpath redirects.

First, create a new catch-all route in the respective subpath: `server/routes/r/[...slug].ts`

Then, in the route, add the following code:

```javascript title="server/routes/r/[...slug].ts" theme={null}
export default defineEventHandler(async (event) => {
  const dubPath = getRouterParam(event, "slug");
  await sendRedirect(event, `https://r.acme.com/${dubPath}`, 301); // use whatever status code you need
});
```

## Using Vercel

If you're using [Vercel](https://vercel.com/), you can use the [`rewrites` property](https://vercel.com/docs/edge-network/rewrites) in your `vercel.json` file to configure subpath redirects.

```json title="vercel.json" theme={null}
{
  "rewrites": [
    {
      "source": "/r/:path*",
      "destination": "https://r.acme.com/:path*"
    }
  ]
}
```

## Using Netlify

If you're using [Netlify](https://www.netlify.com/), you can use the `redirects` property in your `_redirects` file to configure subpath redirects.

```plaintext title="_redirects" theme={null}
/r/* https://r.acme.com/:splat 200
```

## Using Cloudflare

If you're using [Cloudflare](https://www.cloudflare.com/), you can use the [**Redirect Rules** feature](https://developers.cloudflare.com/rules/url-forwarding/single-redirects/create-dashboard/) to configure subpath redirects.

1. Go to your Cloudflare dashboard for your website and click on **Rules > Redirect Rules**.
2. Click on **Create Rule**.
3. Give your rule a name, e.g. "Redirect subpath".
4. Under **When incoming requests match**:
   * Select **Custom filter expression**
   * Field: **URI Path**
   * Operator: **starts with**
   * Value: `/r/`
5. Under **Then**:
   * Type: **Dynamic**
   * Expression: `concat("https://r.acme.com/", substring(http.request.uri.path, 3))`
   * Status code: `301`
   * Preserve query string: ✔️
6. Click on **Deploy**.

<Info>
  The number in the `substring` function in the **Expression** field should be
  equal to the number of characters in the subpath. In our case, the subpath is
  `/r/` which has 3 characters, so we use `substring(http.request.uri.path, 3)`.
</Info>

Here's an example of how the rule should look like:

<Frame>
  <img alt="Cloudflare Redirect Rules example" />
</Frame>

## Using Webflow

If you're using [Webflow](https://webflow.com/), you can use the [**Wildcard redirects** feature](https://university.webflow.com/lesson/set-301-redirects-to-maintain-seo-ranking#wildcard-redirect-examples) to configure subpath redirects.

1. Go to **Site settings** > **Publishing tab** > **301 redirects**
2. Add a new redirect with the following settings:
   * **Old path**: `/r/(.*)`
   * **Redirect to page**: `https://r.acme.com/%1`
3. Click on **Save**.

## Using Squarespace

If you're using [Squarespace](https://squarespace.com/), you can use the [**URL Mappings** feature](https://support.squarespace.com/hc/en-us/articles/205815308-URL-mappings#toc-redirect-multiple-blog-posts--events--or-products) to configure subpath redirects.

1. Open the [Developer tools panel](https://account.squarespace.com/project-picker?client_id=helpcenter\&redirect_url=%2Fsettings%2Fdeveloper-tools).
2. Click **URL mappings**.
3. Click into the text field and add the following redirect:

```plaintext theme={null}
/r/[slug] -> https://r.acme.com/[name] 301
```

4. Click **Save**.


# How to use Tags on Dub
Source: https://dub.co/help/article/how-to-use-tags

Easily organize & manage your links with tags on Dub.

Tags are a great way to organize your links on Dub. You can use tags to group links together and make it easier to find them later.

<Frame>
  <img alt="Using tags to organize links on Dub" />
</Frame>

## What is a Tag?

A tag is a label that you can assign to a link. Tags are color-coded and can be created and assigned to a link in just a few clicks.

With tags, you can:

1. Organize your links by campaigns, clients, or any other categories you can think of.
2. Filter your links by tags and get a shareable link to the filtered results.
3. [Filter your analytics by tags](/help/article/filter-analytics-by-tags) to see how your campaigns are performing.

## How to create a Tag

To create a tag, navigate to the [Tags settings page](https://app.dub.co/settings/library/tags) in your Dub workspace.

<Frame>
  <img alt="Tags settings page" />
</Frame>

Click on **Create Tag** to open up the tag creation modal, where you can create a new tag and choose a color for it:

<Frame>
  <img alt="Tag creation modal" />
</Frame>

You can also create new tags directly inside the [Dub link builder](/help/article/dub-link-builder). Once you've [created a link](/help/article/how-to-create-link), click on the tag input field and type in the name of the tag you want to create.

<Frame>
  <img alt="Input the name of the tag you want to create (e.g. 'New Tag')" />
</Frame>

If the tag already exists, you can select it from the dropdown. Otherwise, press `Enter ↩` to create a new tag.

Once the tag is created, click on the "Create Link" button to assign the tag to the link.

## How to edit or delete a Tag

Tags can be edited via the [Tags settings page](https://app.dub.co/settings/library/tags) in your Dub workspace.

<Frame>
  <img alt="Editing tags in the Tags settings page" />
</Frame>

Simply click on the `⋮` button to open the menu popover for the tag, which gives you the following options:

* Edit the tag
* Copy the tag's unique ID (for [programmatic API use](/help/article/how-to-use-tags#managing-tags-programmatically))
* Delete the tag

<Frame>
  <img alt="Edit a tag by clicking on the ⋮ icon" />
</Frame>

You can also use the following keyboard shortcuts to manage your tags:

| Keyboard Shortcut | Action                   |
| ----------------- | ------------------------ |
| `E`               | Edit a tag               |
| `I`               | Copy the tag's unique ID |
| `X`               | Delete a tag             |

To open the tag editing modal, click on the "Edit" button. Inside the tag editing modal, you can:

* Rename the tag
* Change the color of the tag

<Frame>
  <img alt="Tag editing modal" />
</Frame>

## Can I assign multiple tags to a link?

Yes you can. In the tags section for a given link, you can select multiple tags to assign to the link.

<Frame>
  <img alt="Assign multiple tags to a link" />
</Frame>

The tags will show up in the link's details, and you can filter your links by these tags.

<Frame>
  <img alt="Multiple tags assigned to a link" />
</Frame>

## How many tags can I create?

The number of tags you can create depends on your plan:

* **Free plan**: Up to 5 tags
* [**Pro plan**](https://dub.co/pricing): Up to 25 tags
* [**Business plan**](https://dub.co/pricing): Unlimited tags
* [**Enterprise plan**](https://dub.co/enterprise): Unlimited tags

Check out our [pricing page](https://dub.co/pricing) for more information on our plans.

## Managing tags programmatically

You can also manage tags programmatically using the [Dub API](/docs/api-reference/introduction).

Here are a few things you can do with the API:

* [Create a New Tag](/docs/api-reference/tags/create)
* [Retrieve a List of Tags](/docs/api-reference/endpoint/retrieve-a-list-of-tags)


# Importing partners
Source: https://dub.co/help/article/importing-partners

Learn how to import partners from supported platforms into your Dub program.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

## Where to import partners

In the **Partner Program** menu, click **All Partners** to open your program's partner table.

<Frame>
  <img alt="All Partners location" />
</Frame>

## Importing partners

From the partner table, click the `⋮` menu to import partners from supported platforms into your Dub program.

<Frame>
  <img alt="Importing partners" />
</Frame>

Dub currently supports importing from:

* [Rewardful](/help/article/migrating-from-rewardful)
* [PartnerStack](/help/article/migrating-from-partnerstack)
* [FirstPromoter](/help/article/migrating-from-firstpromoter)
* [Tapfiliate](/help/article/migrating-from-tapfiliate)
* [Tolt](/help/article/migrating-from-tolt)

If you’d like to import from other platforms, [let us know](https://dub.co/contact/support).


# Why are my analytics country or city data incorrect?
Source: https://dub.co/help/article/incorrect-analytics-geolocation-data

Learn why the clicks on your links might be showing up in a different country/city than expected.

When you're [configuring your custom domain](/help/article/how-to-add-custom-domain), you might run into an issue where clicks on your links might be showing up in a different country/city than expected.

This is most likely because your domain is using a proxy in front of Dub (e.g. [Cloudflare Proxy](/help/article/using-cloudflare-domains#using-cloudflares-proxy)).

This is because using a proxy will send all your click traffic through the proxy
first, then to Dub. This will result in incorrect geolocation data being
presented and the public IP address of your proxy being sent.

To fix this, you should remove the proxy – this will make sure that click requests from your users will be sent directly to Dub and will be accurately recorded.

In Cloudflare's case, you can [use Cloudflare's DNS only mode](/help/article/using-cloudflare-domains#using-cloudflares-dns-only-recommended) instead of using their proxy:

<Frame>
  <img alt="" />
</Frame>


# Inviting partners to your program
Source: https://dub.co/help/article/inviting-partners

Learn how to invite partners to join your affiliate/referral program on Dub Partners

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

There are a few ways you can invite partners to join your affiliate/referral program on Dub Partners:

1. [Inviting via the dashboard](#inviting-via-the-dashboard)
2. [Enrolling a partner automatically](#enrolling-a-partner-automatically)
3. [Via a branded application form](#via-a-branded-application-form)

## Inviting via the dashboard

The easiest way to invite a partner to join your program is by sending them an invitation email.

To do that, go to your [program's](https://app.dub.co/programs) Partners tab and select **Invite Partner**.

<Frame>
  <img alt="Merge account button" />
</Frame>

This will open the partner invite sheet, where you can enter the partner's name, email, and optionally select the [group](/help/article/partner-groups) they're being added to.

<Frame>
  <img alt="Partner invite form" />
</Frame>

### Bulk invitations

Invite multiple partners at once by separating their email addresses with commas. Each partner will receive an individual email with a unique link to accept the invitation.

<Frame>
  <img alt="Bulk invite email addresses" />
</Frame>

### Updating invitation email

You're also able to edit the email that is sent to the invited partner. To do this, click **Edit** on the email preview section, and open the editor.

<Frame>
  <img alt="Partner email invite editor" />
</Frame>

You can change the email subject, title, and content for the invite. Changes made here don't replace the default content.

In the content editor you can change the styling of the text using the Bold and Italic buttons at the bottom, or add a link to your text. If you prefer markdown, we also support the following styles:

| Type              | Markdown                         |
| ----------------- | -------------------------------- |
| Bold              | `**Bold text**`                  |
| Italic            | `*italic text*`                  |
| Inline Hyperlinks | `[Dub](https://dub.co)`          |
| Bulleted lists    | `1. First item` `2. Second item` |
| Ordered lists     | `- First item` `- Second item`   |
| Blockquote        | `> Content shows here`           |

Click **Save** to update the email content, or **Cancel** to revert back to the default content.

Once your invite is ready to send, click on **Send invite**. Your partner will receive an email in their inbox to join your program.

<Tip>
  Want to send these invites using your company email instead for better
  brandability? [Set up your email domain on
  Dub](/help/article/email-campaigns#email-domain-setup) (e.g.
  `partners.acme.com`) and start sending branded emails to your partners.
</Tip>

### Viewing past invites

To view your previous invites, click the **Filter** button in the top left of the Partners table, click **Status**, then **Invited**.

<Frame>
  <img alt="" />
</Frame>

This will filter the partners by your invited partners, where you can resend your previous invites if needed.

<Info>
  To prevent spamming inboxes, you can only resend an invite every 24 hours.
</Info>

## Enrolling a partner automatically

Instead of sending an invitation email and waiting for them to accept it, you can also automatically enroll known partners in your program.

<Frame>
  <video />
</Frame>

A few use cases for this approach:

* Programmatically adding your apps users to your [in-app referral program](/docs/partners/embedded-referrals)
* Migrating your partners over from another platform like [Rewardful](/help/article/migrating-from-rewardful)

If you're integrating with Dub Partners programatically, you can use our [POST /partners API endpoint](/docs/api-reference/endpoint/create-a-partner) to enroll a partner into your program via our API.

<Warning>
  Before auto-enrolling a partner to your program, make sure to get their
  consent – either explicitly or via the acceptance of your Terms of Service.
  Enrolling partners that are unfamiliar with your program is against our [terms
  of service](https://dub.co/legal/terms).
</Warning>

## Via a branded application form

Alternatively, you can also have your partners apply via a branded application form:

<Frame>
  <video />
</Frame>

Learn more about [how to create a branded application form](/help/article/program-application-form) with Dub Partners.


# How to use link cloaking on Dub?
Source: https://dub.co/help/article/link-cloaking

Learn how to use link cloaking on Dub to mask your destination URL with your short link.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

With Dub, you can mask your destination URL with your short link.

To enable link cloaking, click on the more options button `...` in the link builder and select the **Add Link Cloaking** option.

<Frame>
  <img alt="Toggling the 'Link Cloaking' switch in the link builder" />
</Frame>

This is useful when you want to show your brand or custom domain instead of the actual destination URL.

When this is enabled, your short link will be shown in your users' browser address bar when they visit your link instead of the destination URL.

<Frame>
  <img alt="Masking 'steventey.com' with 'stey.me'" />
</Frame>

## Link cloaking caveats

A few caveats for the link cloaking feature:

1. For link cloaking to work, make sure you use `https` for your destination URL. If your destination URL is `http`, the browser will show a "Not Secure" warning, and link cloaking will not work.
2. Link cloaking might not work for certain websites that have security measures in place to prevent this:

* `X-Frame-Options` header set to `DENY`
* `content-security-policy` header set to `frame-ancestors 'none'`

## Link cloaking with security headers

If you have control over the destination URL that you are cloaking, you can leverage security headers to enable link cloaking on Dub while also disabling iframe embedding everywhere else.

### Adding security headers to your destination URL

To do this, you need to whitelist your [Dub short domain](/help/article/how-to-add-custom-domain) as an allowed origin on your site by adding the following response headers to your site:

```bash theme={null}
Content-Security-Policy: frame-ancestors 'self' [shortdomain.com]
```

For example, if your Dub short domain is `dub.sh`, you would need to add the following headers to your destination URL:

```bash theme={null}
Content-Security-Policy: frame-ancestors 'self' dub.sh
```

### Whitelisting multiple domains

You can also whitelist multiple domains by separating them with a space:

```bash theme={null}
Content-Security-Policy: frame-ancestors 'self' dub.sh otherdomain.com
```

This will ensure that your site cannot be embedded in third-party sites but can still be cloaked by your Dub short domain.

### Secure link cloaking demo

Here's a demo of how this works:

<ImageLink alt="Link cloaking with security headers demo" href="https://link-cloaking-security.vercel.app" />

* Code example: [git.new/zAD0CkJ](https://git.new/zAD0CkJ)
* Demo site: [link-cloaking-security.vercel.app](https://link-cloaking-security.vercel.app)
* Demo short link (enabled): [dub.link/secure-cloak](https://dub.link/secure-cloak)
* Any other iframes (disabled): [iframetester.com/?url=https://link-cloaking-security.vercel.app](https://iframetester.com/?url=https://link-cloaking-security.vercel.app)


# How to add comments to your links in Dub?
Source: https://dub.co/help/article/link-comments

Learn how to add comments to your links in Dub to provide more context.

On Dub, you can add comments to your links by using the "Comments" section in the link builder.

<Frame>
  <img alt="Adding comments to your links in the Dub link builder" />
</Frame>

<Info>
  Note: Hyperlinks are supported in comments. You can use this to add more
  context to your links.
</Info>

When you add a comment to your link, it will be visible to you and your team as a
message bubble on the link card.

<Frame>
  <img alt="Comments are visible as message bubbles on the link card" />
</Frame>


# Does Dub allow for double link redirects?
Source: https://dub.co/help/article/link-double-redirects

Yes, Dub allows for double link redirects – where you can easily create short links for any brand you work with, improving brand recognition and revenue.

[Content creators](https://dub.co/solutions/creators) on Dub create thousands of short links daily for the brands they work with. Oftentimes, these brands would also use their own link shorteners/affiliate links, which the creator would then shorten with [their own branded domains](/help/article/how-to-add-custom-domain).

However, platforms like [PixelMe](https://www.pixelme.app/) restrict this, which means you're forced to use the generic short links used by the brands you work with.

On Dub, **double link directs work out of the box**. This means you can easily create short links for any brand you work with, which gives you several benefits:

1. [**Improved brand recognition**](https://dub.co/blog/custom-domains): Instead of sharing a generic `brandname.xyz` link, you can share something like `yourname.link/brand`, which helps builds trust with your audience.
2. [**Real-time click analytics**](https://dub.co/analytics): Dub gives you accurate real-time click data that you can leverage to negotiate higher revenue with the brands you work with.


# How to set an expiration date for your Dub links?
Source: https://dub.co/help/article/link-expiration

Learn how to use automatically expire your Dub links after a certain date & time.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

On Dub, you can set an expiration date for your links by clicking the **Expiration** button in the link builder. This will open a new modal called **Link Expiration**.

You can also quickly access the Link Expiration feature by using the keyboard shortcut `E` while in the link builder.

Enter the following fields to set the expiration:

1. **Date and Time**: Date and time when the link will expire.
2. **Expiration URL**: The URL that the user will be redirected to if the link expires.

Now click on the **Add expiration** button to set the expiration date and time for your link.

You can use natural language to set the date and time. For example, you can type "in 2 hours" or "tomorrow at 5PM" and Dub will automatically set the expiration date in the correct date-time format for you.

<Frame>
  <video />
</Frame>

Alternatively, you can also set the expiration date manually by selecting the date and time from the date-time picker.

If you set an expiration date for your link, the user will see an "Expired Link" page when they try to access the link after the expiration date ([example](https://dub.sh/expired)).

<Frame>
  <img alt="Expired link page" />
</Frame>

<Warning>
  Note: All clicks to the link after the expiration date will ***not*** be
  tracked.
</Warning>

## Setting a custom expiration URL

You can also set a custom expiration URL for your links. This is useful if you want to redirect users to a specific page when the link expires.

To set a custom expiration URL, simply enter a custom URL in the **Expiration URL** field under the **Link Expiration** section.

<Frame>
  <img alt="Expired link page" />
</Frame>

## Setting a default expiration URL for all links under a domain

Alternatively, you can also set a default expiration URL for all links under a domain. This is useful if you want to redirect users to a specific page when *any link under the domain* expires.

Here's how you can set a default expiration URL for all links under a domain:

1. Go to the [Domains settings page](https://app.dub.co/domains) in your Dub dashboard.
2. Click on the `⋮` button of domain that you want to set the default expiration URL for, which will open a dropdown menu.

<Frame>
  <img alt="More options popover" />
</Frame>

4. In the dropdown menu, select the **Edit Domain** option, which will open the Edit Domain modal.
5. Under the **Default Expiration URL** field, enter the URL that you want to redirect users to when any link under the domain expires.

<Frame>
  <img alt="Default expiration URL for a domain" />
</Frame>

7. Click on the **Save changes** button to save the changes.


# Managing links with folders
Source: https://dub.co/help/article/link-folders

Learn how to organize and manage access to your links on Dub using folders.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

Folders are a great way to organize and manage access to your links on Dub.

With folders, you can:

1. **Organize links by marketing channels**. E.g. if you're using Dub for both your [affiliate program](https://dub.co/partners) and for SMS marketing, you can create folders for both so those links doesn't clutter your main links dashboard.
2. **Manage access to links across your team**. E.g. if you have marketing teams from different departments within your Dub workspace, you can create folders for each team with [role-based access controls](/help/article/folders-rbac) to ensure members can only see the links that they should have access to.

<Frame>
  <img alt="Link folders" />
</Frame>

In this guide, we'll walk through how to use folders to organize and manage access to your links on Dub.

## Creating a folder

There are a few ways you can create a new folder:

<Steps>
  <Step title="In your links dashboard">
    You can quickly create a new folder within your links dashboard:

    <Frame>
      <img alt="New folder links dashboard" />
    </Frame>
  </Step>

  <Step title="In the link builder">
    You can also create a new folder directly inside the [link builder](/help/article/dub-link-builder):

    <Frame>
      <img alt="New folder link builder" />
    </Frame>
  </Step>

  <Step title="In the folder library">
    Last but not least, you can also create a folder inside your [folder library](http://app.dub.co/settings/library/folders):

    <Frame>
      <img alt="New folder link builder" />
    </Frame>
  </Step>
</Steps>

Creating a folder is fairly simple. First, give it a name:

<Frame>
  <img alt="Create new folder" />
</Frame>

Then, you can also choose the [workspace-level permission](/help/article/folders-rbac) for the folder:

<Frame>
  <img alt="Create new folder access" />
</Frame>

## Adding links to a folder

To add links to a folder, simply select the folder you want to move the link to within the [link edit page](https://dub.co/changelog/link-pages) and click "Save changes":

<Frame>
  <img alt="Move link to folder" />
</Frame>

Alternatively, you can also move a link to a folder directly within the links dashboard:

<Frame>
  <img alt="Move link to folder shortcut" />
</Frame>

Last but not least, you can use [bulk link actions](/help/article/bulk-link-actions) to move multiple links to a folder at once:

<Frame>
  <img alt="Move link to folder bulk" />
</Frame>

## Setting a default folder

Once you've created a few folders, you can choose one to show up by default when you open your links dashboard.

To do that, click the `❯` button next to the **Create link** button to open the folder quick view. Then click the `⋮` dropdown and select the "Set as Default" option:

<Frame>
  <img alt="Set default folder" />
</Frame>

You can also do this inside your [folder library](http://app.dub.co/settings/library/folders):

<Frame>
  <img alt="Set default folder library" />
</Frame>

## How many folders can I create?

The number of folders you can create depends on your plan:

* **Pro plan**: Up to 3 folders
* **Business plan**: Up to 20 folders
* **Advanced plan**: Up to 50 folders
* **Enterprise plan**: Unlimited folders

Check out our [pricing page](https://dub.co/pricing) for more information on our plans.

## Managing folders programmatically

You can also manage folders programmatically using the [Dub API](/docs/api-reference/introduction).

Here are a few things you can do with the API:

* [Create a folder](/docs/api-reference/endpoint/create-a-folder)
* [Update a folder](/docs/api-reference/endpoint/update-a-folder)
* [Delete a folder](/docs/api-reference/endpoint/delete-a-folder)
* [Retrieve a list of folders](/docs/api-reference/endpoint/retrieve-a-list-of-folders)


# Inviting your partner team
Source: https://dub.co/help/article/managing-partner-teams

Learn how to invite team members, assign roles, and manage access to your partner profile on Dub.

On Dub, Partner teams let you share access to your [Dub partner profile](/help/article/partner-profile) with the people you work with. This is useful for creators, agencies, or anyone who wants more than one person managing partner activity.

## Where to manage your team

To manage your partner team, select the **Partner profile** icon in the left sidebar, then navigate to the **Members** tab.

<Frame>
  <img alt="Partner members" />
</Frame>

This view lists everyone with access, including their email and assigned role.

## Roles and permissions

Partner team members support two role types: owner and member.

### Owner

The Owner has full control of the partner account. This includes:

* [Updating partner profile settings](/help/article/partner-profile)
* Managing team members (inviting and removing members)
* [Managing payout details](/help/article/receiving-payouts)

### Member

Members have standard access. They can view and manage partner activity across all programs, but cannot manage team members or payout details.

## Inviting a new member

To invite a new member to your partner team, click the **Invite member** button in the top right.

<Frame>
  <img alt="Invite member" />
</Frame>

Enter the member's email address, and select their role. You can invite more than one member at a time, and they'll each receive their own invite email to accept membership.

<Frame>
  <img alt="Invite details" />
</Frame>

<Info>You can only invite up to 10 teammates per partner team.</Info>

## Changing a role

Only Owner-roled members in a partner team can change another member’s role. To change a member's role, click the role dropdown in the members table and select a new role.

<Frame>
  <img alt="Role change" />
</Frame>

This will open the confirmation modal, where you can confirm the update:

<Frame>
  <img alt="Confirm role change" />
</Frame>

## Removing a member

To remove a member from your team, click the `⋯` dropdown and select **Remove member**.

<Frame>
  <img alt="Remove team partner" />
</Frame>

This deletes the team member, and the person can no longer sign in to your partner profile.


# Partner details
Source: https://dub.co/help/article/managing-program-partners

Learn how to view and manage all your program partners, and view individual partners' profiles.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

## Where to find your program partners

In the **Partner Program** menu, click **All Partners** to see everyone in your program.

<Frame>
  <img alt="Programs partners location" />
</Frame>

## Partner table

The partner table gives you a complete view of everyone in your program. By default, partners are sorted by **net revenue**, starting with your highest performers. This makes it easy to see who is driving the most value at a glance.

<Frame>
  <img alt="Partners table" />
</Frame>

You can sort the table by any column header. For example, select **Conversions** to sort partners by the number of conversions instead of revenue.

### Columns

You can customize which columns appear in the partner table. This lets you keep the table focused on the partner attributes and performance metrics that matter most to your workflow.

| Column        | Description                                               |
| ------------- | --------------------------------------------------------- |
| Group         | The group this partner is in                              |
| Tags          | Tags assigned to this partner                             |
| Enrolled      | The date the partner enrolled in your program             |
| Location      | The partner's declared location                           |
| Clicks        | Total number of clicks on the partner's links             |
| Leads         | Total number of leads generated by the partner's links    |
| Conversions   | Total number of leads that converted to paying customers  |
| Revenue       | Total revenue generated by the partner's links            |
| Commissions   | Total commissions paid to the partner for their referrals |
| Net Revenue   | Net revenue after commissions                             |
| EPC           | Earnings Per Click                                        |
| Avg LTV       | Average lifetime value for each paying customer           |
| Click -> Lead | Percentage of clicks that become leads                    |
| Click -> Conv | Percentage of clicks that convert to paying customers     |
| Lead -> Conv  | Percentage of leads that convert to paying customers      |
| ROAS          | Return On Ad Spend                                        |

<Frame>
  <img alt="Partners table" />
</Frame>

Click the `Gear icon` in the top right of the table to enable or disable columns at any time.

### Filter and search

Use filters to sort partners by group, status, or location. Filters help you spot top-performing partners.

You can also use the search field to search by name, email, or link. This is helpful when looking for a specific partner.

<Frame>
  <img alt="Partner filters" />
</Frame>

You can also use [partner tags](/help/article/partner-tags) to organize partners with flexible labels and filter your partner table by internal workflows, campaigns, or relationship type.

**Status filter**

Filter by status to see who’s active and who isn’t.

| Status      | Description                                                                                                                                                            |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Approved    | Partner has joined and can earn rewards                                                                                                                                |
| Invited     | Partners that have been invited to your program but haven't accepted the invite yet                                                                                    |
| Declined    | Partners that have declined your invitation to join                                                                                                                    |
| Archived    | Partner has been removed from the program                                                                                                                              |
| Banned      | Partners that you have banned from your program                                                                                                                        |
| Deactivated | Partners that you've deactivated your partnership with. This could be used for ending a partnership for non abuse-related reasons (e.g. commercial/financial reasons). |

### More dropdown

For each partner, click the `⋯` dropdown menu to see more controls.

<Frame>
  <img alt="Partner controls" />
</Frame>

These additional controls include:

* **Approved partners**: View commissions, Change group, Update tags, Archive partner, Deactivate partner, and Ban partner
* **Invited partners**: Resend invite and Delete invite
* **Archived partners**: View commissions, Change group, Update tags, Unarchive partner, Deactivate partner, and Ban partner.
* **Banned partners**: View commissions, Change group, Update tags, Unban partner, and [Permanently delete](#deleting-partners).
* **Deactivated partners**: View commissions, Change group, Update tags, Reactivate partner, Ban partner, and [Permanently delete](#deleting-partners).

## Viewing partner details

In the **Partner Program** menu, click **All Partners**, then click on a partner's row to open their details page. This page includes their profile, performance, referral links, payouts, comments, commissions, and [tags](/help/article/partner-tags).

<Frame>
  <img alt="Partner details" />
</Frame>

### Links

To see all referral links for this partner, click **Links**. You'll also see partner discount codes if they have been added. Learn more about partner [discount codes](/help/article/dual-sided-incentives#option-2-using-stripe-promo-codes-no-code-required).

<Frame>
  <img alt="Partner links" />
</Frame>

If you need to create a link for your partner, click **Create link**.

<Frame>
  <img alt="Create partner link" />
</Frame>

### Payouts

Click **Payouts** to see all payouts for a partner. Click any payout row to view its details.

<Frame>
  <img alt="Partner payouts" />
</Frame>

### Profile

To view the partner's profile details, click the **Profile** button next to their name in the right column.

<Frame>
  <img alt="Partner profile button" />
</Frame>

This will open a panel where you can view details like their country, bio, and connected websites or social accounts. You'll also be able to view the partner's original application response when they first applied.

<Frame>
  <img alt="About the partner" />
</Frame>

### Comments

Click **Comments** to view and add comments for this partner, which only your workspace can view. You can also edit and delete your own comments in case a mistake was made.

<Frame>
  <img alt="Partner comments" />
</Frame>

### Commissions

Click **Commissions** to open a page showing commissions filtered for this partner. [Learn more about partner commissions here](/help/article/partner-commissions).

<Frame>
  <img alt="Partner commissions" />
</Frame>

### Manual commission

Occasionally, you might need to create a one-off or recurring commission to account for a missed commission or other reasons. On Dub, you can easily create those commissions via the dashboard, and they will be included in the next payout for that partner.

Click **Create commission** to get started. [Learn more about manual commissions here](/help/article/partner-commissions#creating-manual-commissions).

<Frame>
  <img alt="Create commission" />
</Frame>

### Advanced settings

<Warning>
  Use caution when changing any of the advanced partner settings.
</Warning>

To access your partner's advanced settings, from their profile page, click the `⋮` dropdown and select **Advanced settings**.

<Frame>
  <img alt="Advanced settings" />
</Frame>

#### Tenant ID

If you need to update the partner's tenant ID, changing the ID here will update the [tenantId for all their links](/docs/concepts/links/organization#tenant-id) as well. This could potentially impact any custom integration you have set up, so only change if you're certain the information is correct and necessary.

#### Customer data sharing

Enabling this setting allows this partner to see customer data on any leads or sales the partner has made.

Instead of the hidden email addresses, they will see the full customer email address and their name. They'll also be able to click through the customer to view the sales history.

<Frame>
  <img alt="Partner customer view" />
</Frame>

#### Ignore group move rules

Enabling this setting allows this partner to stay within the set group, and ignore any [group move rules](/help/article/partner-groups#moving-partners-between-groups) that may apply to them.

#### Exclude from risk monitoring

Enabling this setting will exclude this partner from any risk monitoring, and they won't be able to be flagged for any [risk events](/help/article/risk-monitoring). Only enable this setting if you trust this partner completely.

## Exporting partners

Click the `⋮` menu and choose **Export as CSV** to download your partner data for use anywhere.

<Frame>
  <img alt="Partners dropdown" />
</Frame>

## Deleting partners

To permanently delete a partner, they must meet the following criteria:

* The partner is `banned` or `deactivated` status
* The partner must have no `clicks`, `leads`, `sales`, or `commissions`

If the criteria is met, click the `⋯` dropdown menu and select `Permanently delete`.

<Frame>
  <img alt="Delete partner" />
</Frame>

Permanently deleting a partner also deletes all of their assigned links and disables their discount codes. This action cannot be undone.

<Info>
  Partners with activity cannot be deleted because their records are needed to preserve accurate attribution, analytics, commission history, and reporting. If a partner has activity but should no longer participate in your program, deactivate or ban them instead. This removes their access while keeping your historical program data intact.
</Info>


# Merging your partner accounts
Source: https://dub.co/help/article/merging-partner-accounts

Learn how to merge one of your partner accounts with another, to keep things organized and better managed.

<Warning>
  Merging your accounts is a permanent action and cannot be undone once it's complete.
</Warning>

## Reasons to merge accounts

There are some reasons why you want to merge your partner accounts on Dub:

* **Consolidate accounts:** You were enrolled in 2 affiliate programs on Dub under different emails.
* **Simplify management**: You want to be able to manage your payouts, programs, and reporting all from one place.
* **Stay compliant**: For compliance reasons, having multiple partner accounts on Dub is strictly prohibited [as per our terms](https://dub.co/legal/partners). We recommend merging your partner accounts to stay compliant.

## What merges?

All of the following merge with this action:

* [](/help/article/commissions-payouts#payout-statuses)[Enrolled programs](/help/article/navigating-partner-program)
* [Referral links](/help/article/navigating-partner-program#links)
* [Analytics](/help/article/navigating-partner-program#analytics) (clicks, leads, and sales data)
* [Commissions](/help/article/commissions-payouts#commission-statuses)
* [Payouts](/help/article/commissions-payouts#payout-statuses)

## What doesn't merge?

* [Profile info](/help/article/partner-profile)
* [Website and social accounts](/help/article/partner-profile#website-and-socials)
* [Connected payout methods](/help/article/receiving-payouts)
* Duplicate bounty submissions (the ones on the source account will be deleted)

## Where to find the merge option

From your **Partner info** page, click **Merge accounts** in the top right, next to **Save changes**.

<Frame>
  <img alt="Merge account dropdown" />
</Frame>

Clicking this will open a dialog window that has 3 steps to merging your account.

## Steps to merge

<Steps>
  <Step title="Confirm account email addresses">
    Enter the email addresses of the accounts you'd like to merge. You'll need to have access to both email accounts to verify them.

    <Frame>
      <img alt="Source and target account emails" />
    </Frame>

    There are two fields to complete:

    * **Source account** - the email address of the account you want to merge into the `target account`
    * **Target account** - the email address of the account you want the `source account` to merge into

    Once these have been added, click **Send codes** to continue the verification process.
  </Step>

  <Step title="Verify the email addresses">
    We'll send an email to each address with a OTP (one-time password) that will be input on this step.

    The emails will look like this:

    <Frame>
      <img alt="One-time password email" />
    </Frame>

    Enter the OTP for each email address into their required field, and click
    **Verify accounts** to confirm the codes.

    <Frame>
      <img alt="Email verification" />
    </Frame>
  </Step>

  <Step title="Final merge confirmation">
    This is the final step to confirm the accounts are correct and that you still want to merge. At this point, you're still able to cancel the merge, and nothing will change.

    If everything looks good, click **Merge accounts**, and the merge will be completed instantly.

    <Frame>
      <img alt="Final confirmation" />
    </Frame>

    After the merge is complete, an email will be sent to both accounts confirming the merger.
  </Step>
</Steps>


# Messaging your partners
Source: https://dub.co/help/article/messaging-partners

Learn how to use the messaging center to communicate with your partners in real time, with email notifications & read statuses built in.

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

On Dub, you can use the Messaging Center to communicate directly with the partners in your program. Partners get notified via email and can respond to your messages in real-time.

<VideoPlayer title="Dub Messaging Center" />

## Messages location

In your program navigation, click **Messages** to see your inbox and compose new messages.

<Frame>
  <img alt="Messages location" />
</Frame>

## Messages overview

The messaging center has three panels:

<Frame>
  <img alt="Message panels" />
</Frame>

| Panel        | Content                                         |
| ------------ | ----------------------------------------------- |
| Inbox        | All your conversations in one place.            |
| Conversation | The full thread with your selected partner.     |
| Details      | Key info and performance stats for the partner. |

### Inbox panel

Your Inbox lists every conversation with your partners. Select one to pick up where you left off, or click the **Pencil** button in the top right to compose something new.

<Frame>
  <img alt="Inbox panel" />
</Frame>

Each conversation row shows:

* **Unread indicator** – shows if you've read the message
* **Partner** – partner's name and profile image
* **Last activity** – most recent message from you or the partner
* **Excerpt** – short preview of the latest message

<Frame>
  <img alt="Inbox pieces" />
</Frame>

### Conversation panel

In the Conversation panel, you’ll see the full thread with your partner. Workspace members can reply too, with their profile images shown beside each message.

<Frame>
  <img alt="Conversation panel" />
</Frame>

### Message attachments

Messages can also include file attachments. This is helpful for sharing things like contracts, guidelines, or promotional materials.

To add attachments, click the paper clip button in the message composer or drag
and drop files into the message area.

<Frame>
  <img alt="Message attachments" />
</Frame>

Supported attachment types are `PNG`, `JPG/JPEG`, `WEBP`, and `PDF` files. Each attachment can be up to 10 MB, and you can send up to 5 attachments per message.

<Frame>
  <img alt="Message attachments sent" />
</Frame>

### Message status

Each message sent has a check mark next to the send time:

* **Single check mark** – delivered

* **Double check marks** – delivered and read
  (either in email or in the app)

### Details panel

The details panel is collapsible and shows partner information and performance stats. You can also change their program group here.

<Frame>
  <img alt="Details panel" />
</Frame>

## Disabling messaging

If you prefer to handle support outside Dub, you can disable partner messaging.

To disable messaging, head to your program **Resources** and in the **Help and Support** section, click the switch to off to disable partner messaging.

<Frame>
  <img alt="Disable messages" />
</Frame>

When messaging is disabled, partners who try to message you will see your support email address instead:

<Frame>
  <img alt="Details panel" />
</Frame>


# Migrating from Bitly
Source: https://dub.co/help/article/migrating-from-bitly

Easily migrate your links from Bitly to Dub in less than 3 minutes.

Bitly is a popular URL shortener that lets you create short links, which can be used in your marketing campaigns, social media posts, and more.

## How to migrate from Bitly to Dub

If you're looking to migrate from Bitly to Dub, we've built a custom migration tool that will help you migrate your links in just 2-3 clicks.

<Steps>
  <Step title="Sign in with Bitly">
    If you haven't already, [create a workspace on Dub](/help/article/what-is-a-workspace#how-to-create-a-workspace).

    <Info>
      **Protip**: Once you create your workspace, we generally recommend you ***not
      to [configure your domain](/help/article/how-to-add-custom-domain)*** until
      you've migrated all your links. This will prevent any potential downtime for
      your links.
    </Info>

    Then, from your workspace dashboard, click on the `⋮` button next to the **Create Link** button and select **Import from Bitly**.

    <Frame>
      <img alt="Import from Bitly button" />
    </Frame>

    This will open a modal that prompts you to sign in with Bitly.

    <Frame>
      <img alt="Import from Bitly modal" />
    </Frame>

    In the sign in screen, click "Allow" and you'll be redirected back to Dub.

    <Frame>
      <img alt="Signing in with Bitly" />
    </Frame>

    Behind the scenes, we'll fetch all your Bitly groups and their respective custom domains and number of tags. You'll be presented with a list of groups that we've found.

    <Frame>
      <img alt="List of Bitly groups found for your account" />
    </Frame>

    <Info>
      If you accidentally signed in to the wrong Bitly account, you can click on the
      **Sign in with a different account?** link to sign in with a different Bitly
      account.
    </Info>
  </Step>

  <Step title="Select the domains you want to migrate">
    Select the domains you want to migrate and choose if you'd like to import their respective tags as well. You can learn more about [how tags work on Dub](/help/article/how-to-use-tags).

    Once you're done, click **Confirm import**. In just a few seconds, all your links will be migrated to Dub.

    <Frame>
      <img alt="Links imported from Bitly" />
    </Frame>

    Once all your links have been imported, we will send you an email to let you know that the migration is complete.

    <Frame>
      <img alt="Email notification about completed Bitly migration" />
    </Frame>
  </Step>

  <Step title="Configure your domain">
    Once you receive the email, you can [configure your domain](/help/article/how-to-add-custom-domain#step-2-configure-your-domain) by setting the right DNS records. This will ensure that your links are pointing to Dub and not Bitly. Once you've done this, you can start using your links!
  </Step>
</Steps>

## Which Bitly link attributes are migrated?

Here's a list of attributes that are migrated from Bitly to Dub:

1. Destination URL
2. Short link slug
3. Link creation date
4. [Link tags](/help/article/how-to-use-tags)
5. Archived status

If there are any other attributes that you'd like to see migrated, please [reach out to us](https://dub.co/contact/support) and let us know!


# Migrating from FirstPromoter
Source: https://dub.co/help/article/migrating-from-firstpromoter

Easily migrate your affiliate program from FirstPromoter to Dub Partners in just a few clicks.

<Tip>
  Dub Partners is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

FirstPromoter is a practical option for companies just starting out with their affiliate programs.

Over time, companies outgrow FirstPromoter's basic features and look for more advanced solutions to overcome some limitations, including:

1. **Frequent payout failures**: FirstPromoter supports multiple payout methods. PayPal and Wise mass payments are manual and often result in payout failures. Companies must handle tax compliance themselves, which becomes time-consuming as their affiliate base grows. FirstPromoter also offers 1-click global payouts, but this feature is only available on their [Enterprise 2 plan or higher](https://help.firstpromoter.com/en/articles/9998853-how-managed-auto-payouts-work).
2. **No affiliate network**: Since FirstPromoter doesn't offer an affiliate network, companies have to recruit affiliates on their own.
3. **Basic reward structures:** Companies can choose between two reward types: recurring or one-time sales rewards.

**With [Dub Partners](https://dub.co/partners), scaling affiliate programs is seamless.**

1. **Payouts you can rely on:** We let you [pay your partners globally](/help/article/partner-payouts) in 1-click, with tax compliance built-in, so you can have peace of mind knowing that all payouts are completed successfully.
2. **Powerful affiliate network**: With over 5,000 active affiliates in our network, Dub makes it easy to find and recruit high-performing partners with a proven track record of driving results.
3. **Advanced reward structures**: On Dub, you can choose between [3 reward types](/help/article/partner-rewards#configuring-reward-types) – per click, per lead, and per sale (flat/percentage). You can also set different [reward conditions](/help/article/partner-rewards#adding-reward-conditions) (e.g., a higher reward for customers based in the U.S. vs other countries) and create [bounties](/help/article/program-bounties) to provide additional rewards for your affiliates.

<Tip>
  [Learn how Framer switched from FirstPromoter to
  Dub](https://dub.co/customers/framer) to power 7,000+ global affiliates and
  \$900,000+ in monthly payouts.
</Tip>

In this article, we'll learn how to import your FirstPromoter affiliate program to[ Dub Partners](/help/article/dub-partners) for a more powerful and scalable partner management experience.

## How to migrate from FirstPromoter to Dub?

If you're looking to migrate from FirstPromoter to Dub Partners, we've built a custom migration tool that will help you migrate everything over in just 2-3 clicks – without breaking any existing FirstPromoter affiliate links.

<Steps>
  <Step title="Add a custom domain to your Dub workspace">
    Before starting the migration process, you'll need to add a custom domain to Dub. This domain will be used to create short links for each affiliate link that we import from FirstPromoter.

    If you haven't added a custom domain to Dub yet, you can [follow this guide to set that up](/help/article/how-to-add-custom-domain).
  </Step>

  <Step title="Set up conversion tracking + client-side click-tracking">
    Next, [set up conversion tracking](/docs/quickstart/server) to track signup and sale events. We also have some [in-app guides that you can reference here](https://app.dub.co/guides).

    For backward compatibility, we also recommend setting up [client-side click-tracking](/docs/sdks/client-side/features/click-tracking) when installing the [Dub Analytics script](/docs/sdks/client-side/introduction) on your site. This will ensure all your existing `domain.com?fp_ref=affiliate` links will keep working as expected, which gives affiliates time to update their links to the new format.

    You can [follow our guide](/docs/sdks/client-side/features/click-tracking) on how to set up client-side click-tracking for your Dub short links to set that up.
  </Step>

  <Step title="Get your FirstPromoter API Key and Account ID">
    [Follow this guide](https://docs.firstpromoter.com/api-reference-v1/authentication) to get your FirstPromoter API key – API keys can be created in the [Settings tab of your FirstPromoter dashboard](https://app.firstpromoter.com/settings/integrations).

    You will also need to get your FirstPromoter Account ID under your [Settings tab](https://app.firstpromoter.com/settings/integrations) as well.

    We will use the API key and Account ID to retrieve and import your existing FirstPromoter affiliate program campaigns to Dub.
  </Step>

  <Step title="Import your FirstPromoter campaign in the Partners tab">
    Once you've [set up your Dub partner program](/help/article/setting-up-your-program), go to your [Partners tab](https://app.dub.co/program/partners), click on the `⋮` button and select "Import from FirstPromoter".

    To save you some time, [here's a quicklink](https://app.dub.co/program/partners?import=firstpromoter) that will get you directly to the FirstPromoter import flow.

    <Frame>
      <img alt="Import FirstPromoter modal" />
    </Frame>

    Then, add your FirstPromoter API key + Account ID and select **Continue.**
  </Step>
</Steps>

## What data is migrated?

Our FirstPromoter migration tool imports the following attributes to Dub Partners:

### Campaigns / Groups

Each of your [campaigns](https://help.firstpromoter.com/en/articles/8971305-what-are-campaigns) on FirstPromoter will be mapped to a [partner group](/help/article/partner-groups) on Dub.

<Info>
  Since FirstPromoter doesn't expose a `/rewards` endpoint, your FirstPromoter
  program terms will not be migrated – all partners will be assigned to your
  program's default reward and you would need to manually update the reward terms afterwards.

  Learn more about how [Partner
  rewards](/help/article/partner-rewards) work on Dub.
</Info>

### Promoters / Partners

To ensure the efficiency of your partner program, we automatically filter out dormant promoters and only import the ones that are both:

* In an "accepted" state ([docs](https://docs.firstpromoter.com/api-reference-v2/api-admin/promoters/get-available-promoters#parameter-filters-state))
* Have at least 1 referral (`referrals_count > 1` → [docs](https://docs.firstpromoter.com/api-reference-v2/api-admin/promoters/get-available-promoters#parameter-filters-referrals-count))

We also automatically import all your promoters' links and recreate them in Dub using your program's domain and default URL.

### Referrals / Customers

By default, we import all referrals that are associated with an affiliate in your FirstPromoter program. This ensures that all subsequent subscription renewal events will continue to be tracked on Dub.

<Warning>
  Some referrals might not have a valid Stripe customer ID. Those referrals will
  still be imported, with the Stripe customer ID updated afterwards.
</Warning>

### Commissions

We also import all commissions associated with any customers that were imported above. Depending on the commission state on FirstPromoter, they will be marked accordingly on Dub as well:

| FirstPromoter | Dub         |
| ------------- | ----------- |
| `Pending`     | `Pending`   |
| `Approved`    | `Processed` |
| `Denied`      | `Canceled`  |

Dub also supports 4 additional commission states:

* `Paid`: When the payout containing the commission has been paid.
* `Refunded`: If the sale for the commission was refunded.
* `Fraud`: Useful for marking a sale as fraudulent and excluding it from any future payouts.
* `Duplicate`: Useful for marketing if a commission was rewarded more than once and needs to be marked as a duplicate event.

<Warning>
  Commissions for a referral that do not have a valid Stripe customer ID or a
  transaction trace will not be imported.
</Warning>

If there are any other attributes that you'd like to see migrated (or if you would like to customize any of the rules above), please [reach out to us](https://dub.co/contact/support) and let us know!

## Next steps

After importing your program, here are some recommended next steps:

### 1. Set up your bank account for partner payouts

Dub Partners supports [1-click payouts](/help/article/partner-payouts) to partners all across the globe.

We generally recommend using ACH for partner payouts as it is the most cost-effective option. Here's [how you can set up your bank account](/help/article/how-to-set-up-bank-account) for ACH payouts.

<ImageLink href="https://dub.co/help/article/how-to-set-up-bank-account" alt="connect bank account" />

### 2. Notify your affiliates about the platform change

Next, you'd want to reach out to your existing partners and let them know about the platform change. To do that, you can send them an email with the following example verbiage:

> Hey,
>
> Starting today, we are switching the \[Your Company] Affiliate Program from FirstPromoter to [Dub Partners](https://dub.co/partners/).
>
> To get started, follow these steps:
>
> 1. Sign up for a Dub Partners account here: `partners.dub.co/{your_program_slug}/register`
> 2. [Connect a payout method](https://partners.dub.co/settings/payouts) to receive your commissions.
> 3. Start sharing your shiny new referral link with your audience 🚀
>
> Let us know if you have any questions!

## Take your affiliate program to the next level with Dub

Ready to take your affiliate program to the next level? [Sign up today](https://app.dub.co/register) and join companies like [Framer](https://dub.co/customers/framer) that have migrated from FirstPromoter to Dub.

Have any questions? Feel free to [reach out to us](https://dub.co/contact)!


# Migrating from PartnerStack
Source: https://dub.co/help/article/migrating-from-partnerstack

Easily migrate your affiliate program from PartnerStack to Dub Partners in just a few clicks.

<Tip>
  Dub Partners is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

PartnerStack is a go-to affiliate management platform for many businesses worldwide.

While widely used, PartnerStack has its limitations that are especially noticeable if you are just starting your affiliate program:

1. **Pricing**: Companies must contact sales for a quote, with the average customer paying [\$18,000](https://www.vendr.com/marketplace/partnerstack) annually, plus a ‘take fee’ on affiliate payouts that can be [as high as 15%](http://d.to/partnerstack-fees). These costly annual contracts lock you in before your affiliate program has a chance to scale.
2. **Complex UI**: PartnerStack has every feature you can think of, resulting in dense navigation, buried settings, and workflows that require documentation to understand.
3. **Affiliate network**: PartnerStack has one of the largest affiliate networks with 65,000+ users. But customer feedback consistently shows that most of these ‘partners’ generate no results. You have to manually go through thousands of profiles to find the few who actually drive revenue.

**This is where [Dub Partners](https://dub.co/partners) comes in.**

1. **Pricing that scales with your success**: You can start your affiliate program on Dub with [as little as \$90/month](https://dub.co/pricing/partners) and only 5% payout fees. As you grow, payout fees drop to 3%, giving you the flexibility to reward partners without high costs eating into your margins.
2. **Modern UI with enterprise-grade capabilities**: Dub's modern interface makes it simple for both you and your partners to navigate without a learning curve. You can use our [Embedded Referral Dashboard](/docs/partners/embedded-referrals) to create a seamless referral experience, or leverage our best-in-class [Risk Monitoring](/help/article/risk-monitoring) feature to ensure the safety/compliance of your program. The possibilities are endless.
3. **Powerful affiliate network**: With over 5,000 meticulously vetted affiliates in our partner network, Dub makes it easy to find and recruit high-performing partners with a proven track record of driving results.

In this article, we'll learn how to import your PartnerStack affiliate program to[ Dub Partners](/help/article/dub-partners) for a more powerful, scalable, and branded partner management experience.

## How to migrate from PartnerStack to Dub?

If you're looking to migrate from PartnerStack to Dub Partners, we've built a custom migration tool that will help you migrate everything over in just 2-3 clicks – without breaking any existing PartnerStack affiliate links.

<Steps>
  <Step title="Add a custom domain to your Dub workspace">
    Before starting the migration process, you'll need to add a custom domain to Dub. This domain will be used to create short links for each affiliate link that we import from PartnerStack.

    If you haven't added a custom domain to Dub yet, you can [follow this guide to set that up](/help/article/how-to-add-custom-domain).
  </Step>

  <Step title="Set up conversion tracking">
    Next, [set up conversion tracking](/docs/quickstart/server) to track signup and sale events. We also have some [in-app guides that you can reference here](https://app.dub.co/guides).
  </Step>

  <Step title="Get your PartnerStack API Keys">
    [Follow this guide](https://docs.tolt.com/authentication) to get your PartnerStack API keys – API keys can be created in the [Settings → API](https://app.partnerstack.com/settings/integrations) tab of your PartnerStack dashboard:

    <Frame>
      <img alt="PartnerStack API key" />
    </Frame>

    <Tip>
      Make sure to use your production API keys to import live partners only (since
      test partners won't be imported).
    </Tip>

    We will use the generated Public and Secret keys to retrieve and import your existing PartnerStack program to Dub.
  </Step>

  <Step title="Import your PartnerStack program in the Partners tab">
    Once you've [set up your Dub partner program](/help/article/setting-up-your-program), go to your [Partners tab](https://app.dub.co/program/partners), click on the `⋮` button and select "Import from PartnerStack".

    To save you some time, [here's a quicklink](https://app.dub.co/program/partners?import=partnerstack) that will get you directly to the PartnerStack import flow.

    <Frame>
      <img alt="Import PartnerStack modal" />
    </Frame>

    Then, add your PartnerStack Public and Secret keys and select **Continue.**
  </Step>
</Steps>

## What data is migrated?

Our PartnerStack migration tool imports the following attributes to Dub Partners:

### Affiliates / Partners

To ensure the efficiency of your partner program, we automatically filter out dormant affiliates and only import the ones that are both:

* In an "active" state ([docs](https://developers.rewardful.com/rest-api/affiliates/object#state))
* Have at least 1 lead / referral

We also automatically import all your affiliates' links and recreate them in Dub using your program's domain and default URL.

<Info>
  Since PartnerStack doesn't expose a `/triggers` endpoint, your PartnerStack triggers will not be migrated – all partners will be assigned to your program's default reward.

  Learn more about how [Partner rewards](/help/article/partner-rewards) work on Dub.

  Feel free to [reach out to support](https://dub.co/contact/support) if you need help manually configuring specific reward groups for your importer partners.
</Info>

### Referrals / Customers

By default, we import all referrals that are associated with an affiliate in your PartnerStack campaign. This ensures that all subsequent subscription renewal events will continue to be tracked on Dub.

Certain customers will not be imported, such as test accounts or any customer that is not associated with an affiliate link.

<Warning>
  Some referrals might not have a valid Stripe customer ID. Those referrals will
  still be imported, with the Stripe customer ID updated afterwards.
</Warning>

### Commissions

We also import all commissions associated with any customers that were imported above. Depending on the commission state on PartnerStack, they will be marked accordingly on Dub as well:

| PartnerStack | Dub         |
| ------------ | ----------- |
| `Pending`    | `Pending`   |
| `Approved`   | `Processed` |
| `Paid`       | `Paid`      |
| `Declined`   | `Canceled`  |
| `Hold`       | `Fraud`     |

Additionally, Dub also supports 2 additional commission states:

* `Refunded`: When the sale for the commission was refunded. With our [Stripe integration](/docs/integrations/stripe), this is automatically detected and updated.
* `Duplicate`: Useful for marketing if a commission was rewarded more than once and needs to be marked as a duplicate event.

<Warning>
  Commissions for a referral that do not have a valid Stripe customer ID or a
  transaction trace will not be imported.
</Warning>

If there are any other attributes that you'd like to see migrated (or if you would like to customize any of the rules above), please [reach out to us](https://dub.co/contact/support) and let us know!

## Next steps

After importing your campaign, here are some recommended next steps:

### 1. Set up your bank account for partner payouts

Dub Partners supports [1-click payouts](/help/article/partner-payouts) to partners all across the globe.

We generally recommend using ACH for partner payouts as it is the most cost-effective option. Here's [how you can set up your bank account](/help/article/how-to-set-up-bank-account) for ACH payouts.

<ImageLink href="https://dub.co/help/article/how-to-set-up-bank-account" alt="connect bank account" />

### 2. Notify your affiliates about the platform change

Next, you'd want to reach out to your existing partners and let them know about the platform change. To do that, you can send them an email with the following example verbiage:

> Hey,
>
> Starting today, we are switching the \[Your Company] Affiliate Program from PartnerStack to [Dub Partners](https://dub.co/partners/).
>
> To get started, follow these steps:
>
> 1. Sign up for a Dub Partners account here: `partners.dub.co/{your_program_slug}/register`
> 2. [Connect a payout method](https://partners.dub.co/settings/payouts) to receive your commissions.
> 3. Start sharing your shiny new referral link with your audience 🚀
>
> Let us know if you have any questions!

## Take your affiliate program to the next level with Dub

Ready to take your affiliate program to the next level? [Sign up today](https://app.dub.co/register) and join companies like [Privy](https://partners.dub.co/privy) that have migrated from PartnerStack to Dub.

Have any questions? Feel free to [reach out to us](https://dub.co/contact)!


# Migrating from Rebrandly
Source: https://dub.co/help/article/migrating-from-rebrandly

How you can migrate from Rebrandly to Dub in a few simple steps.

Rebrandly is a popular URL shortener that lets you create short links, which can be used in your marketing campaigns, social media posts, and more.

## How to migrate from Rebrandly to Dub

If you're looking to migrate from Rebrandly to Dub, we've built a custom migration tool that will help you migrate your links in a few simple steps.

<Steps>
  <Step title="Get your Rebrandly API key">
    First, log into your Rebrandly account and navigate to [Account > API](https://app.rebrandly.com/account/api).

    <Image alt={`"Account > API" section in Rebrandly`} />

    If you don't have an API key yet, click on "New API Key", which will create a new API key for you.

    Copy the created API key. You'll need it in the next step.

    <Frame>
      <img alt="Copy the created API key" />
    </Frame>
  </Step>

  <Step title="Use the Rebrandly migration tool">
    If you haven't already, [create a workspace on Dub](/help/article/what-is-a-workspace#how-to-create-a-workspace).

    <Info>
      **Protip**: Once you create your workspace, we generally recommend you ***not
      to [configure your domain](/help/article/how-to-add-custom-domain)*** until
      you've migrated all your links. This will prevent any potential downtime for
      your links.
    </Info>

    Then, from your workspace dashboard, click on the `⋮` button next to the **Create Link** button and select **Import from Rebrandly**.

    <Frame>
      <img alt="Import from Rebrandly button" />
    </Frame>

    You'll be prompted to enter your Rebrandly API key. Paste the API key you copied in the previous step and click **Confirm API Key**.

    <Frame>
      <img alt="Import from Rebrandly modal" />
    </Frame>

    Behind the scenes, we'll fetch all your Rebrandly custom domains and their respective number of links. You'll be presented with a list of domains that we've found.

    <Frame>
      <img alt="Domains found in Rebrandly and their respective number of links" />
    </Frame>
  </Step>

  <Step title="Select the domains you want to migrate">
    Select the domains you want to migrate. You can also choose if you'd like to import their respective tags as well. You can learn more about [how tags work on Dub](/help/article/how-to-use-tags).

    One you're done, click **Confirm import**. In just a few seconds, all your links and their respective tags will be migrated to Dub.

    <Frame>
      <img alt="Links imported from Rebrandly" />
    </Frame>

    Once all your links have been imported, we will send you an email to let you know that the migration is complete.

    <Frame>
      <img alt="Email notification about completed Rebrandly migration" />
    </Frame>
  </Step>

  <Step title="Configure your domain">
    Once you receive the email, you can [configure your domain](/help/article/how-to-add-custom-domain#step-2-configure-your-domain) by setting the right DNS records. This will ensure that your links are pointing to Dub and not Rebrandly. Once you've done this, you can start using your links!
  </Step>
</Steps>

## Which Rebrandly link attributes are migrated?

Here's a list of attributes that are migrated from Rebrandly to Dub:

1. Destination URL
2. Short link slug
3. Link creation date
4. Link updated date
5. [Link tags](/help/article/how-to-use-tags)
6. Link title (as part of Dub's [Custom Link Previews](/help/article/custom-link-previews) feature)

Here are the attributes that are not migrated:

1. [Device targeting](/help/article/device-targeting)
2. [Geo targeting](/help/article/geo-targeting)
3. Deep links

If there are any attributes that you'd like to see migrated, please [reach out to us](https://dub.co/contact/support) and let us know!


# Migrating from Rewardful
Source: https://dub.co/help/article/migrating-from-rewardful

Easily migrate your affiliate program from Rewardful to Dub Partners in just a few clicks.

<Tip>
  Dub Partners is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Rewardful is a popular affiliate management platform, especially for businesses that use Stripe for payments.

However, Rewardful can be pretty limiting for companies that want to run their affiliate programs at scale:

1. **No payouts support**: Companies generally have to resort to using PayPal/Wise to pay out their affiliates manually and deal with the all-too-common payout failures.
2. **Basic reward structures:** Companies can choose between two reward types: recurring or one-time sales rewards.
3. **Limited white-labeling support**: It's not possible to embed Rewardful's dashboard inside your application.
4. **No affiliate network**: Rewardful is a standalone affiliate software, which means you're on your own when it comes to finding and recruiting high-quality affiliates to join your program.

**This is where [Dub Partners](https://dub.co/partners) comes in.**

1. **Payouts you can rely on:** We let you [pay your partners globally](/help/article/partner-payouts) in 1-click, with tax compliance built-in, so you can have peace of mind knowing that all payouts are completed successfully.
2. **Advanced reward structures**: On Dub, you can choose between [3 reward types](/help/article/partner-rewards#configuring-reward-types) – per click, per lead, and per sale (flat/percentage). You can also set different [reward conditions](/help/article/partner-rewards#adding-reward-conditions) (e.g., a higher reward for customers based in the U.S. vs other countries) and create [bounties](/help/article/program-bounties) to provide additional rewards for your affiliates.
3. **White-labeling**: You can [embed a white-labeled referral dashboard into your application](/docs/partners/embedded-referrals) for a better user experience.
4. **Powerful affiliate network**: With over 5,000 active affiliates in our network, Dub makes it easy to find and recruit high-performing partners with a proven track record of driving results.

<Info>
  Case in point: [Tella](https://partners.dub.co/tella) migrated their affiliate program from Rewardful to Dub and saw a [38% increase in revenue and 2x more partner signups](https://dub.co/customers/tella).

  Read the [full comparison article between Dub and Rewardful here](https://dub.co/blog/dub-vs-rewardful).
</Info>

In this article, we'll learn how to import your Rewardful affiliate program to[ Dub Partners](/help/article/dub-partners) for a more powerful, scalable, and branded partner management experience.

## How to migrate from Rewardful to Dub?

If you're looking to migrate from Rewardful to Dub Partners, we've built a custom migration tool that will help you migrate everything over in just 2-3 clicks – without breaking any existing Rewardful affiliate links.

<Steps>
  <Step title="Add a custom domain to your Dub workspace">
    Before starting the migration process, you'll need to add a custom domain to Dub. This domain will be used to create short links for each affiliate link that we import from Rewardful.

    If you haven't added a custom domain to Dub yet, you can [follow this guide to set that up](/help/article/how-to-add-custom-domain).
  </Step>

  <Step title="Set up conversion tracking + client-side click-tracking">
    Next, [set up conversion tracking](/docs/quickstart/server) to track signup and sale events. We also have some [in-app guides that you can reference here](https://app.dub.co/guides).

    For backward compatibility, we also recommend setting up [client-side click-tracking](/docs/sdks/client-side/features/click-tracking) when installing the [Dub Analytics script](/docs/sdks/client-side/introduction) on your site. This will ensure all your existing `domain.com?via=affiliate` links will keep working as expected, which gives affiliates time to update their links to the new format.

    You can [follow our guide](/docs/sdks/client-side/features/click-tracking) on how to set up client-side click-tracking for your Dub short links to set that up.
  </Step>

  <Step title="Get your Rewardful API Secret">
    Go to your [Company settings page on Rewardful](https://app.getrewardful.com/company/edit) to retrieve your Rewardful API secret:

    <Frame>
      <img alt="Rewardful API secret" />
    </Frame>

    We will use this API key to retrieve and import your existing Rewardful campaign to Dub.
  </Step>

  <Step title="Import your Rewardful campaign in the Partners tab">
    Once you've [set up your Dub partner program](/help/article/setting-up-your-program), go to your [Partners tab](https://app.dub.co/program/partners), click on the `⋮` button and select "Import from Rewardful".

    To save you some time, [here's a quicklink](https://app.dub.co/program/partners?import=rewardful) that will get you directly to the Rewardful import flow.

    <Frame>
      <img alt="Import Rewardful modal" />
    </Frame>

    Then, add your Rewardful API secret and select **Fetch campaigns**. From the retrieved list of Rewardful campaigns, select the main campaign you want to import first and click **Continue**.

    <Tip>
      If you have multiple campaigns on Rewardful, you can [import multiple
      campaigns](#migrating-multiple-campaigns) into Dub as well.
    </Tip>
  </Step>
</Steps>

## What data is migrated?

Our Rewardful migration tool imports the following attributes to Dub Partners:

### Program terms

Depending on the Rewardful campaign you import, we'll automatically configure the same reward rules on your Dub partner program:

* Commission type (dollar amount or percentage) and amount
* Commission duration (e.g. up to 12 months or lifetime recurring)

### Affiliates / Partners

To ensure the efficiency of your partner program, we automatically filter out dormant affiliates and only import the ones that are both:

* In an "active" state ([docs](https://developers.rewardful.com/rest-api/affiliates/object#state))
* Have at least 1 lead / referral

We also automatically import all your affiliates' links and recreate them in Dub using your program's domain and default URL.

### Referrals / Customers

By default, we import all referrals that are associated with an affiliate in your Rewardful campaign. This ensures that all subsequent subscription renewal events will continue to be tracked on Dub.

<Warning>
  Referrals that do not have a valid Stripe customer ID will not be imported.
</Warning>

### Commissions

We also import all commissions associated with any customers that were imported above. Depending on the commission state on Rewardful, they will be marked accordingly on Dub as well:

| Rewardful | Dub        |
| --------- | ---------- |
| `Pending` | `Pending`  |
| `Due`     | `Pending`  |
| `Paid`    | `Paid`     |
| `Voided`  | `Canceled` |

Additionally, Dub also supports 3 additional commission states:

* `Processed`: When a commission has been added to a [payout](/help/article/partner-payouts) (after any applicable holding period).
* `Refunded`: When the sale for the commission was refunded. With our [Stripe integration](/docs/integrations/stripe), this is automatically detected and updated.
* `Fraud`: Useful for marking a sale as fraudulent and excluding it from any future payouts.
* `Duplicate`: Useful for marketing if a commission was rewarded more than once and needs to be marked as a duplicate event.

<Warning>
  Similar to referrals, commissions for a referral that do not have a valid
  Stripe customer ID will not be imported.
</Warning>

If there are any other attributes that you'd like to see migrated (or if you would like to customize any of the rules above), please [reach out to us](https://dub.co/contact/support) and let us know!

## Migrating multiple campaigns

If needed, you can migrate multiple Rewardful campaigns to Dub. We recommend starting with the main campaign, and then restarting the import flow to import the remaining campaigns. Our importer is built in an idempotent fashion, so it will import everything only once.

To start another import flow, go to your program's **Partners** tab and click on the `⋮` button next to **Invite partner**. From the dropdown, select **Import from Rewardful**.

<Frame>
  <img alt="Import from Rewardful" />
</Frame>

This will open up the Rewardful importer, which you can then use to import more campaigns to Dub.

<Frame>
  <img alt="Import Rewardful modal" />
</Frame>

Each campaign that is migrated to Dub will create a new [partner group](/help/article/partner-groups) with the imported partners. If needed, you can also delete the imported partner groups to consolidate partners under your main program reward.

## Next steps

After importing your campaign, here are some recommended next steps:

### 1. Set up your bank account for partner payouts

Dub Partners supports [1-click payouts](/help/article/partner-payouts) to partners all across the globe.

We generally recommend using ACH for partner payouts as it is the most cost-effective option. Here's [how you can set up your bank account](/help/article/how-to-set-up-bank-account) for ACH payouts.

<ImageLink href="https://dub.co/help/article/how-to-set-up-bank-account" alt="connect bank account" />

### 2. Notify your affiliates about the platform change

Next, you'd want to reach out to your existing partners and let them know about the platform change. To do that, you can send them an email with the following example verbiage:

> Hey,
>
> Starting today, we are switching the \[Your Company] Affiliate Program from Rewardful to [Dub Partners](https://dub.co/partners/).
>
> To get started, follow these steps:
>
> 1. Sign up for a Dub Partners account here: `partners.dub.co/{your_program_slug}/register`
> 2. [Connect a payout method](https://partners.dub.co/settings/payouts) to receive your commissions.
> 3. Start sharing your shiny new referral link with your audience 🚀
>
> Let us know if you have any questions!

## Take your affiliate program to the next level with Dub

Ready to take your affiliate program to the next level? [Sign up today](https://app.dub.co/register) and join companies like [Beehiiv](https://partners.dub.co/programs/marketplace/beehiiv), [Chatbase](https://partners.dub.co/chatbase), [Tella](https://dub.co/customers/tella), and more who have migrated from Rewardful to Dub.

Have any questions? Feel free to [reach out to us](https://dub.co/contact)!


# Migrating from Short.io
Source: https://dub.co/help/article/migrating-from-short

How you can migrate from Short.io to Dub in a few simple steps.

Short.io is a popular URL shortener that lets you create short links, which can be used in your marketing campaigns, social media posts, and more.

## How to migrate from Short.io to Dub

If you're looking to migrate from Short.io to Dub, we've built a custom migration tool that will help you migrate your links in a few simple steps.

<Steps>
  <Step title="Get your Short.io API key">
    First, log into your Short.io account and navigate to the [Integrations & API](https://app.short.io/settings/integrations/api-key) page.

    <Frame>
      <img alt="" />
    </Frame>

    Click on "Create API Key", which will open up a modal. Leave all the options as default and click "Create".

    <Frame>
      <img alt="API key creation modal in Short.io" />
    </Frame>

    Copy the created API key. You'll need it in the next step.

    <Frame>
      <img alt="Copy the created API key" />
    </Frame>
  </Step>

  <Step title="Use the Short.io migration tool">
    If you haven't already, [create a workspace on Dub](/help/article/what-is-a-workspace#how-to-create-a-workspace).

    <Info>
      **Protip**: Once you create your workspace, we generally recommend you ***not
      to [configure your domain](/help/article/how-to-add-custom-domain)*** until
      you've migrated all your links. This will prevent any potential downtime for
      your links.
    </Info>

    Then, from your workspace dashboard, click on the `⋮` button next to the **Create Link** button and select **Import from Short.io**.

    <Frame>
      <img alt="Import from Short.io button" />
    </Frame>

    You'll be prompted to enter your Short.io API key. Paste the API key you copied in the previous step and click **Confirm API Key**.

    <Frame>
      <img alt="Import from Short.io modal" />
    </Frame>

    Behind the scenes, we'll fetch all your Short.io custom domains and their respective number of links. You'll be presented with a list of domains that we've found.

    <Frame>
      <img alt="Domains found in Short.io and their respective number of links" />
    </Frame>
  </Step>

  <Step title="Select the domains you want to migrate">
    Select the domains you want to migrate and click **Confirm import**. In just a few seconds, all your links will be migrated to Dub.

    <Frame>
      <img alt="Links imported from Short.io" />
    </Frame>

    Once all your links have been imported, we will send you an email to let you know that the migration is complete.

    <Frame>
      <img alt="Email notification about completed Short.io migration" />
    </Frame>
  </Step>

  <Step title="Configure your domain">
    Once you receive the email, you can [configure your domain](/help/article/how-to-add-custom-domain#step-2-configure-your-domain) by setting the right DNS records. This will ensure that your links are pointing to Dub and not Short.io. Once you've done this, you can start using your links!
  </Step>
</Steps>

## Which Short.io link attributes are migrated?

Here's a list of attributes that are migrated from Short.io to Dub:

1. Destination URL
2. Short link slug
3. Link creation date
4. iOS device Targeting (if enabled)
5. Android device targeting (if enabled)
6. Archived status
7. Link title (as part of Dub's [Custom Link Previews](/help/article/custom-link-previews) feature)
8. [Link tags](/help/article/how-to-use-tags)

Here are the attributes that are not migrated:

1. Password protection
2. Link 404 URL
3. Link expiration date
4. Link redirect type (Dub uses 307 redirects for all links)
5. Link cloaking

If there are any attributes that you'd like to see migrated, please [reach out to us](https://dub.co/contact/support) and let us know!


# Migrating from Tapfiliate
Source: https://dub.co/help/article/migrating-from-tapfiliate

Easily migrate your affiliate program from Tapfiliate to Dub Partners in just a few clicks.

<Tip>
  Dub Partners is only available on [Business plans and above](https://dub.co/pricing/partners).
</Tip>

Tapfiliate is a popular affiliate and referral management platform for SMBs that want to track and manage their partnership programs.

However, Tapfiliate has several limitations that become apparent as your program grows:

1. **Affiliate and click caps**: The entry-level plan is capped at 50 affiliates and 5,000 clicks/month, with expensive overage fees. Growing programs quickly find themselves forced into higher-tier plans just to accommodate their affiliate base.
2. **Manual payouts**: Since the payout process is not automated, you will need to manually handle all payouts and tax compliance.
3. **No embedded referral dashboard**: Tapfiliate doesn't support embedding the affiliate dashboard directly inside your application, making for a disjointed partner experience.
4. **Limited affiliate network**: While Tapfiliate has a marketplace, it lacks a curated network of high-performing affiliates – you're largely on your own when it comes to recruiting quality partners.

**This is where [Dub Partners](https://dub.co/partners) comes in.**

1. **Automated payouts:** You can [pay your partners globally](/help/article/partner-payouts) in 1-click, with tax compliance built-in, so you can have peace of mind knowing that all payouts are completed successfully.
2. **Advanced reward structures**: You can choose between [3 reward types](/help/article/partner-rewards#configuring-reward-types) – per click, per lead, and per sale (flat/percentage). You can also set different [reward conditions](/help/article/partner-rewards#adding-reward-conditions) (e.g., a higher reward for customers based in the U.S. vs other countries) and create [bounties](/help/article/program-bounties) to provide additional rewards for your affiliates.
3. **White-labeling**: You can [embed a white-labeled referral dashboard into your application](/docs/partners/embedded-referrals) for a better user experience.
4. **Powerful partner network**: With over 7,000 active partners in our network, Dub makes it easy to find and recruit high-performing partners with a proven track record of driving results.

In this article, we'll learn how to import your Tapfiliate affiliate program to [Dub Partners](/help/article/dub-partners) for a more powerful, scalable, and branded partner management experience.

## How to migrate from Tapfiliate to Dub?

If you're looking to migrate from Tapfiliate to Dub Partners, we have a custom migration tool that will help you migrate everything over in just a few clicks – without breaking any existing Tapfiliate affiliate links.

<Steps>
  <Step title="Add a custom domain to your Dub workspace">
    Before starting the migration process, you'll need to add a custom domain to Dub. This domain will be used to create short links for each affiliate link that we import from Tapfiliate.

    If you haven't added a custom domain to Dub yet, you can [follow this guide to set that up](/help/article/how-to-add-custom-domain).
  </Step>

  <Step title="Set up conversion tracking + client-side click-tracking">
    Next, [set up conversion tracking](/docs/quickstart/server) to track signup and sale events. We also have some [in-app guides that you can reference here](https://app.dub.co/guides).

    For backward compatibility, we also recommend setting up [client-side click-tracking](/docs/sdks/client-side/features/click-tracking) when installing the [Dub Analytics script](/docs/sdks/client-side/introduction) on your site. This will ensure all your existing `domain.com?ref=affiliate` links will keep working as expected, which gives affiliates time to update their links to the new format.

    You can [follow our guide](/docs/sdks/client-side/features/click-tracking) on how to set up client-side click-tracking for your Dub short links to set that up.
  </Step>

  <Step title="Get your Tapfiliate API Key">
    Go to your [Profile settings page on Tapfiliate](https://app.tapfiliate.com/profile/) and scroll down to the **API Key** section to retrieve your Tapfiliate API key:

    <Frame>
      ![Tapfiliate API key](https://assets.dub.co/cms/tapfiliate-api-key.png)
    </Frame>

    We will use this API key to retrieve and import your existing Tapfiliate program to Dub.
  </Step>

  <Step title="Import your Tapfiliate program in the Partners tab">
    Once you've [set up your Dub partner program](/help/article/setting-up-your-program), go to your [Partners tab](https://app.dub.co/program/partners), click on the `⋮` button and select "Import from Tapfiliate".

    To save you some time, [here's a quicklink](https://app.dub.co/program/partners?import=tapfiliate) that will get you directly to the Tapfiliate import flow.

    <Frame>
      ![Import Tapfiliate modal](https://assets.dub.co/cms/import-tapfiliate-modal.png)
    </Frame>

    Enter your Tapfiliate API key and click **Continue**. We'll validate your key and automatically fetch the list of programs in your account. Select the program you want to import and click **Continue** to kick off the import.

    <Tip>
      If you have multiple programs on Tapfiliate, you can [import multiple programs](#migrating-multiple-programs) into Dub as well.
    </Tip>
  </Step>
</Steps>

## What data is migrated?

Our Tapfiliate migration tool imports the following attributes to Dub Partners:

### Affiliate groups

Each of your [affiliate groups](https://support.tapfiliate.com/en/articles/13407284-creating-and-managing-affiliate-groups) on Tapfiliate will be mapped to a [partner group](/help/article/partner-groups) on Dub. Groups with no assigned affiliates are automatically removed after the import.

### Affiliates / Partners

We import all affiliates from your Tapfiliate program, then run a cleanup pass at the end. Any affiliate whose link has **zero leads** – meaning they never referred a single customer – is removed along with their enrollment and link. Affiliates without a Dub account who are not enrolled in any other program are deleted entirely.

In practice, only affiliates who have referred at least one customer will be present in Dub after the import completes.

We also automatically import all your affiliates' links and recreate them in Dub using your program's domain and default URL.

### Customers

By default, we import all customers associated with an affiliate in your Tapfiliate program. This ensures that all subsequent subscription renewal events will continue to be tracked on Dub.

After partners and customers are imported, we run a separate step to match each customer to their Stripe account, so their future payments are attributed correctly.

### Commissions

We also import all commissions associated with any customers that were imported above. Tapfiliate tracks commissions using an `approved` boolean field, which maps to Dub as follows:

| Tapfiliate                  | Dub       |
| --------------------------- | --------- |
| `approved: true`            | `Paid`    |
| `approved: false` or `null` | `Pending` |

Additionally, Dub also supports 3 extra commission states:

* `Processed`: When a commission has been added to a [payout](/help/article/partner-payouts) (after any applicable holding period).
* `Refunded`: When the sale for the commission was refunded. With our [Stripe integration](/docs/integrations/stripe), this is automatically detected and updated.
* `Fraud`: Useful for marking a sale as fraudulent and excluding it from any future payouts.
* `Duplicate`: Useful for marking if a commission was rewarded more than once and needs to be marked as a duplicate event.

<Warning>
  Commissions that cannot be matched to an imported customer, partner link, or click event will not be imported.
</Warning>

If there are any other attributes that you'd like to see migrated (or if you would like to customize any of the rules above), please [reach out to us](https://dub.co/contact/support) and let us know!

## Migrating multiple programs

If needed, you can migrate multiple Tapfiliate programs to Dub. We recommend starting with the main program, and then restarting the import flow to import the remaining programs. Our importer is built in an idempotent fashion, so it will import everything only once.

To start another import flow, go to your program's **Partners** tab and click on the `⋮` button next to **Invite partner**. From the dropdown, select **Import from Tapfiliate**.

<Frame>
  ![Import from Tapfiliate](https://assets.dub.co/cms/import-tapfiliate-modal.png)
</Frame>

This will open up the Tapfiliate importer, which you can then use to import more programs to Dub.

<Info>
  Affiliate groups in Tapfiliate are account-level, not program-level. The importer maps your existing Tapfiliate affiliate groups to Dub [partner groups](/help/article/partner-groups) – re-running the import for a second program will reuse those same groups. Affiliates who are not assigned to any Tapfiliate group are placed into your program's default group.

  If your Tapfiliate account doesn't use affiliate groups, all imported partners will land in the default group regardless of which program they came from.
</Info>

## Next steps

After importing your program, here are some recommended next steps:

### 1. Set up your bank account for partner payouts

Dub Partners supports [1-click payouts](/help/article/partner-payouts) to partners all across the globe.

We generally recommend using ACH for partner payouts as it is the most cost-effective option. Here's [how you can set up your bank account](/help/article/how-to-set-up-bank-account) for ACH payouts.

<ImageLink href="https://dub.co/help/article/how-to-set-up-bank-account" alt="connect bank account" />

### 2. Notify your affiliates about the platform change

Next, you'd want to reach out to your existing partners and let them know about the platform change. To do that, you can send them an email with the following example verbiage:

> Hey,
>
> Starting today, we are switching the \[Your Company] Affiliate Program from Tapfiliate to [Dub Partners](https://dub.co/partners/).
>
> To get started, follow these steps:
>
> 1. Sign up for a Dub Partners account here: `partners.dub.co/{your_program_slug}/register`
> 2. [Connect a payout method](https://partners.dub.co/settings/payouts) to receive your commissions.
> 3. Start sharing your shiny new referral link with your audience 🚀
>
> Let us know if you have any questions!

## Take your affiliate program to the next level with Dub

Ready to take your affiliate program to the next level? [Sign up today](https://app.dub.co/register) and join companies like **Beehiiv**, **[Framer](https://dub.co/customers/framer), Superhuman, Granola**, and more that use Dub Partners to run high-performing affiliate programs.

Have any questions? Feel free to [reach out to us](https://dub.co/contact)!


# Migrating from Tolt
Source: https://dub.co/help/article/migrating-from-tolt

Easily migrate your affiliate program from Tolt to Dub Partners in just a few clicks.

<Tip>
  Dub Partners is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Tolt is a go-to affiliate management platform for many early-stage companies.

However, Tolt is limiting for companies that want to run their affiliate programs at scale:

1. **Basic analytics**: While Tolt shows basic clicks and leads, it lacks UTM and referrer data. You see the numbers but not what drives them.
2. **Frequent payout failures**: Tolt offers automated payouts on higher-tier plans, but customers who migrated to Dub reported frequent payout failures. This means spending hours troubleshooting payments and managing frustrated partners.
3. **No affiliate network**: As standalone software, Tolt leaves the task of finding and recruiting high-quality affiliates entirely to you.
4. **Outdated UI:** Tolt’s UI feels outdated, despite the platform positioning itself for SaaS companies, where intuitive, modern dashboards are typically a priority.

**This is where [Dub Partners](https://dub.co/partners) comes in.**

1. **Comprehensive analytics**: Dub offers [powerful real-time analytics](https://dub.co/analytics), including [conversion tracking](/help/article/dub-conversions), device, browser, referrer, country, and more. Our [AI feature](/help/article/dub-analytics#bonus-ask-ai-feature) makes filtering data intuitive and quick.
2. **Payouts you can rely on:** We let you [pay your partners globally](/help/article/partner-payouts) in 1-click, with tax compliance built-in, so you can have peace of mind knowing that all payouts are completed successfully.
3. **Powerful affiliate network**: With over 5,000 active affiliates in our network, Dub makes it easy to find and recruit high-performing partners with a proven track record of driving results.
4. **Advanced reward structures**: On Dub, you can choose between [3 reward types](/help/article/partner-rewards#configuring-reward-types) – per click, per lead, and per sale (flat/percentage). You can also set different [reward conditions](/help/article/partner-rewards#adding-reward-conditions) (e.g., a higher reward for customers based in the U.S. vs other countries) and create [bounties](/help/article/program-bounties) to provide additional rewards for your affiliates.

In this article, we'll learn how to import your Tolt affiliate program to[ Dub Partners](/help/article/dub-partners) for a more powerful, scalable, and branded partner management experience.

## How to migrate from Tolt to Dub?

If you're looking to migrate from Tolt to Dub Partners, we've built a custom migration tool that will help you migrate everything over in just 2-3 clicks – without breaking any existing Tolt affiliate links.

<Steps>
  <Step title="Add a custom domain to your Dub workspace">
    Before starting the migration process, you'll need to add a custom domain to Dub. This domain will be used to create short links for each affiliate link that we import from Tolt.

    If you haven't added a custom domain to Dub yet, you can [follow this guide to set that up](/help/article/how-to-add-custom-domain).
  </Step>

  <Step title="Set up conversion tracking + client-side click-tracking">
    Next, [set up conversion tracking](/docs/quickstart/server) to track signup and sale events. We also have some [in-app guides that you can reference here](https://app.dub.co/guides).

    For backward compatibility, we also recommend setting up [client-side click-tracking](/docs/sdks/client-side/features/click-tracking) when installing the [Dub Analytics script](/docs/sdks/client-side/introduction) on your site. This will ensure all your existing `domain.com?via=affiliate` links will keep working as expected, which gives affiliates time to update their links to the new format.

    You can [follow our guide](/docs/sdks/client-side/features/click-tracking) on how to set up client-side click-tracking for your Dub short links to set that up.
  </Step>

  <Step title="Get your Tolt API Key and Program ID">
    [Follow this guide](https://docs.tolt.com/authentication) to get your Tolt API key – API keys can be created in the [Settings →  Integrations](https://app.tolt.io/settings?tab=integrations) tab of your Tolt dashboard:

    <Frame>
      <img alt="Tolt API key" />
    </Frame>

    You will also need to get your Tolt Program ID under [Program Settings](https://app.tolt.io/program-settings?page=general-settings):

    <Frame>
      <img alt="Tolt Program ID" />
    </Frame>

    We will use the API key and Program ID to retrieve and import your existing Tolt affiliate program to Dub.
  </Step>

  <Step title="Import your Tolt program in the Partners tab">
    Once you've [set up your Dub partner program](/help/article/setting-up-your-program), go to your [Partners tab](https://app.dub.co/program/partners), click on the `⋮` button and select "Import from Tolt".

    To save you some time, [here's a quicklink](https://app.dub.co/program/partners?import=tolt) that will get you directly to the Tolt import flow.

    <Frame>
      <img alt="Import Tolt modal" />
    </Frame>

    Then, add your Tolt API key + Program ID and select **Continue.**
  </Step>
</Steps>

## What data is migrated?

Our Tolt migration tool imports the following attributes to Dub Partners:

### Affiliates / Partners

To ensure the efficiency of your partner program, we automatically filter out dormant affiliates and only import the ones that are both:

* In an "active" state ([docs](https://developers.rewardful.com/rest-api/affiliates/object#state))
* Have at least 1 lead / referral

We also automatically import all your affiliates' links and recreate them in Dub using your program's domain and default URL.

<Info>
  Since Tolt doesn't expose a `/rewards` endpoint, your Tolt program terms will not be migrated – all partners will be assigned to your program's default reward.

  Learn more about how [Partner rewards](/help/article/partner-rewards) work on Dub.
</Info>

### Referrals / Customers

By default, we import all referrals that are associated with an affiliate in your Tolt program. This ensures that all subsequent subscription renewal events will continue to be tracked on Dub.

<Warning>
  Some referrals might not have a valid Stripe customer ID. Those referrals will
  still be imported, with the Stripe customer ID updated afterwards.
</Warning>

### Commissions

We also import all commissions associated with any customers that were imported above. Depending on the commission state on Tolt, they will be marked accordingly on Dub as well:

| Tolt       | Dub        |
| ---------- | ---------- |
| `Pending`  | `Pending`  |
| `Approved` | `Pending`  |
| `Paid`     | `Paid`     |
| `Rejected` | `Canceled` |
| `Refunded` | `Refunded` |

Additionally, Dub also supports 3 additional commission states:

* `Fraud`: Useful for marking a sale as fraudulent and excluding it from any future payouts.
* `Processed`: When a commission has been added to a [payout](/help/article/partner-payouts) (after any applicable holding period).
* `Duplicate`: Useful for marketing if a commission was rewarded more than once and needs to be marked as a duplicate event.

<Warning>
  Commissions for a referral that do not have a valid Stripe customer ID or a
  transaction trace will not be imported.
</Warning>

If there are any other attributes that you'd like to see migrated (or if you would like to customize any of the rules above), please [reach out to us](https://dub.co/contact/support) and let us know!

## Next steps

After importing your program, here are some recommended next steps:

### 1. Set up your bank account for partner payouts

Dub Partners supports [1-click payouts](/help/article/partner-payouts) to partners all across the globe.

We generally recommend using ACH for partner payouts as it is the most cost-effective option. Here's [how you can set up your bank account](/help/article/how-to-set-up-bank-account) for ACH payouts.

<ImageLink href="https://dub.co/help/article/how-to-set-up-bank-account" alt="connect bank account" />

### 2. Notify your affiliates about the platform change

Next, you'd want to reach out to your existing partners and let them know about the platform change. To do that, you can send them an email with the following example verbiage:

> Hey,
>
> Starting today, we are switching the \[Your Company] Affiliate Program from Tolt to [Dub Partners](https://dub.co/partners/).
>
> To get started, follow these steps:
>
> 1. Sign up for a Dub Partners account here: `partners.dub.co/{your_program_slug}/register`
> 2. [Connect a payout method](https://partners.dub.co/settings/payouts) to receive your commissions.
> 3. Start sharing your shiny new referral link with your audience 🚀
>
> Let us know if you have any questions!

## Take your affiliate program to the next level with Dub

Ready to take your affiliate program to the next level? [Sign up today](https://app.dub.co/register) and join companies like [Anara](https://partners.dub.co/anara) that have migrated from Tolt to Dub.

Have any questions? Feel free to [reach out to us](https://dub.co/contact)!


# Navigating a partner program
Source: https://dub.co/help/article/navigating-partner-program

Learn how to navigate your programs, measure your performance, manage your referral links, and view your earnings.

## How to view your joined programs

From the **All** **programs** navigation, click **Programs** to view all the partner programs you've joined. If you haven't joined any programs, you'll see an empty state here.

<Frame>
  <img alt="Programs navigation location" />
</Frame>

There are two ways to view all the pages related to a specific program:

<Steps>
  <Step title="Program grid">
    On the **Programs** page, we show you a grid of all the programs you've
    joined. We also show you your default referral link and your total earnings.
    Click on the card to view that program page.

    <Frame>
      <img alt="Program grid" />
    </Frame>
  </Step>

  <Step title="All programs dropdown">
    The other way you can navigate to your programs is to click **All programs**
    at the top of the sidebar, and that will open a dropdown. From here, you can
    search and click on any of the programs listed. You can also click on **All
    programs** in this dropdown, and that will take you back to the grid view
    anytime.

    <Frame>
      <img alt="Program dropdown" />
    </Frame>
  </Step>
</Steps>

## Program pages

### Overview

This is your main program overview page, is the first page you'll want to check to see how your programs are performing.

<Frame>
  <img alt="Program overview" />
</Frame>

The page has the following sections:

* **Program summary** - find your default referral link and the rewards and discounts you are eligible for on this program. If you want to hide this section, click the **Hide details** button in the top right, and it won't show until you click the button again.
* **Program earnings and payouts** - see how much you've earned over time, and your most recent payouts. The earnings section also allows you to change the date range to view results from further in the past.
* **Link analytics** - View your clicks, leads and sales for this program, keep track of all their activity.
* **Recent earnings** - A detailed breakdown of your most recent earnings and their payout status.

### Links

You'll find all your program referral links on this page, including the clicks, leads, and sales for each.

<Frame>
  <img alt="Program links" />
</Frame>

Clicking on the link card opens a dialog window that will allow you to edit the
short link, destination URL, QR code, and add comments. If you change an
existing short link, be sure to update it wherever it's being shared; otherwise,
the previous short link will stop tracking, and you won't be able to earn any
new rewards for that link.

<Frame>
  <img alt="Link editor" />
</Frame>

From this page, you're also able to create new short links by clicking **Create
link** in the top right of this page.

### Resources

Quite often, programs will provide additional assets and resources to help their partners when sharing their referral links.

<Frame>
  <img alt="Program resources" />
</Frame>

The resources are split into three sections:

1. **Brand logos** - These are the official logo and brand files from the program.
2. **Colors** - Make sure you're using the correct hex values in your campaigns or other marketing assets.
3. **Additional files** - This is for anything else that may fall outside of the brand logos and colors. It could be additional marketing images, documents to read, or campaign examples. If anything is missing that you'll need to be successful, make sure to contact the program's support at the [bottom of the program navigation](/help/article/navigating-partner-program#program-help-and-support).

### Earnings

View your earnings performance over time and the details associated with each item.

<Frame>
  <img alt="Program earnings" />
</Frame>

In the top section, you can change the view type by **Link** or **Type**, to be
able to see which links are performing better, or which reward type has earned
more (pending the program offers rewards for clicks, leads, and sales).

### Analytics

View your program performance analytics, and filter and organize by clicks, leads, and sales.

<Frame>
  <img alt="Program analytics" />
</Frame>

#### Data toggle

You can change the data displayed by clicking the `123 | $` toggle in the top right of the data type section.

By default `$` is selected, as it is related directly to your reward earnings. Switching to `123` will change any monetary values to their specific reward count. If the program you're on has other monetary rewards (clicks, leads, sales), their dollar amount will show as well.

<Frame>
  <img alt="Time-series chart" />
</Frame>

#### Chart toggle

You can change the chart type displayed by clicking the `time-chart | conversion funnel` toggle in the top right of the chart area.

<Frame>
  <img alt="Time-series chart" />
</Frame>

When the chart type is switched to **conversion funnel**, you can view the percentage of customers that complete each stage of the sales conversion. In this example, 0.58% of clicks have resulted in a referral making a purchase.

<Frame>
  <img alt="Time-series chart" />
</Frame>

#### Aggregated data for different facets (top views)

These are more commonly known as the "Top Views" in Dub Analytics. These views show your top links, countries, cities, devices, and more.

<Frame>
  <img alt="Aggregated data for different facets" />
</Frame>

Each section has their own tabs to select different data for you to explore your program data further. These are grouped by the following categories:

* **Links** - Short Links and Destination URLs
* **Referrals** - Referrers and UTM Parameters
* **Location** - Countries, Cities, Regions, and Continents
* **Tech** - Devices, Browser, OS, and Triggers

### Events

View your program events, and filter and organize by clicks, leads, and sales. Dub's real-time events stream is a detailed, real-time stream of events across each program you've joined.

<Frame>
  <img alt="Program events" />
</Frame>

You can filter this data, select a different date range, and change the data
shown by clicking on Clicks, Leads, and Sales in the top section.

## Program help and support

Every program provides email support for its partners and the questions you have that are specific to the program. Optionally, a program may also provide a link to the help documentation and up-to-date terms of service.

<Frame>
  <img alt="Program support" />
</Frame>

The section is always located at the bottom of the program page navigation to
make it easy to find, so you know where to access support at the right moment.

For Dub platform support, reporting bugs, or other partner-related issues, [reach out to us](https://dub.co/contact/support) anytime.


# Dub Network referral bonus
Source: https://dub.co/help/article/network-referral-bonus

Learn how the Dub Network referral bonus works and how you can share your referral link to start earning.

Dub offers a Network referral bonus that lets you earn rewards when you invite other partners to join the Dub partner network and they start earning commissions on Dub.

When another partner joins Dub through your referral link, you can earn **50% of your referred partners' [payout fees](#what-are-payout-fees) for their first 12 months**.

## How to access the Network Referrals page

You can access the Network Referrals page via this permalink: [partners.dub.co/referrals](https://partners.dub.co/referrals)

Alternatively, you can also click on the gift icon in the bottom left corner from the main navigation:

<Frame>
  <img alt="Network referrals navigation" />
</Frame>

## Sharing your referral link

On the Network Referrals page, copy your referral link and share it with partners you want to invite to Dub.

<Frame>
  <img alt="Network referral link" />
</Frame>

Alternatively, you can also link directly to any program on Dub by appending your Dub username to the end of the link.

For example, if your Dub username is `steven` and you want to link to [Dub's affiliate program](https://partners.dub.co/dub), you can share the following link: [`partners.dub.co/dub/apply?via=steven`](https://partners.dub.co/dub/apply?via=steven)

Then, when someone signs up through your link, Dub attributes that partner and all their future commissions for the first 12 months to your Dub partner account.

<Note>
  If the program you're linking to already has a [Partner Referral
  reward](/help/article/partner-referrals) that you're eligible for, the Partner
  Referral reward will take precedence over the Dub Network Referral bonus.
</Note>

## Tracking referrals and earnings

The Dub [Network Referrals page](https://partners.dub.co/referrals) shows:

* **Referred partners** – the number of partners who joined Dub through your referral link
* **Your earnings** – the referral bonus earnings attributed to your network referrals
* **Referral rewards** – the current network referral reward terms
* **Referred partner details** – the partner's join date, location, active programs, and your earnings from that partner

<Frame>
  <img alt="Network referrals dashboard" />
</Frame>

## FAQ

### How do I get paid for my network referrals?

Network referral rewards are paid through your Dub partner account – similar to how you receive commissions from the programs you're working with.

To receive your network referral rewards, make sure you have [connected your payout method](/help/article/receiving-payouts). Once your rewards are eligible for payout, they follow the same payout process as your other Dub partner earnings.

### What are payout fees?

Whenever a program sends a payout to their partners on Dub, we charge a small processing fee that averages around 3% per payout.

For example: if you refer a partner who earns **\$20,000 in commissions**, their payout fees will be **\$600**, and you will earn 50% of that, which is **\$300**.

The more high quality partners you refer, the more you can earn in network referral rewards – there is no limit to how much you can earn.

### Can I link to any program on Dub even if I'm not an active partner in that program?

Yes, you can link to any program on Dub even if you're not an active partner in that program – all you need to do is append your Dub username to the end of the link.

For example, if your Dub username is `steven` and you want to link to [Dub's affiliate program](https://partners.dub.co/dub), you can share the following link: [`partners.dub.co/dub/apply?via=steven`](https://partners.dub.co/dub/apply?via=steven)

<Note>
  Caveat: If the program you're linking to already has a [Partner Referral
  reward](/help/article/partner-referrals) that you're eligible for, the Partner
  Referral reward will take precedence over the Dub Network Referral bonus.
</Note>


# Non-monetary rewards
Source: https://dub.co/help/article/non-monetary-rewards

Learn how to create non-monetary rewards (e.g. free credits, free months, etc.) for your referral partners.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

With Dub Partners, you can create non-monetary rewards for your partners, such as:

* Free usage credits for every new user they refer
* Free month of service for every new user they refer – up to 12 months

This is specifically useful for referral programs – especially when you're using the [embedded referral dashboard](/docs/partners/embedded-referrals) – since it gives you an easy way to incentivize your existing userbase to refer new users to your product.

<Note>
  While non-monetary rewards are easy to set up, we've seen that cash incentives are generally more impactful/sustainable in the long run – especially when you're working with influencers and larger affiliates.

  To get the best of both worlds, you can potentially start your users on a [non-monetary referral partner group](#step-1-set-up-a-separate-partner-group-for-non-monetary-rewards) and [graduate them to a monetary reward group](#graduating-partners-from-non-monetary-rewards-to-monetary-rewards) once they've referred a certain number of users.
</Note>

## Step 1: Set up a separate partner group for non-monetary rewards

First, you would want to create a separate [partner group](/help/article/partner-groups) for your user referral program.

In this group, you can set up a [non-monetary reward](/help/article/partner-rewards) that can be either a lead or a sale-based reward:

* Lead reward: Rewarding partners for new user signups (or qualified leads if you're using [deferred lead tracking](/docs/conversions/leads/deferred))
* Sale reward: Rewarding partners for paid customer referrals

When creating the reward, you would want to set the reward amount to \$0 and add a [custom description](/help/article/partner-rewards#custom-reward-descriptions) to explain the reward to your partners:

<Frame>
  <img alt="Lead reward description tooltip" />
</Frame>

<Tip>
  You can also set up a [custom reward
  tooltip](/help/article/partner-rewards#custom-reward-descriptions) which
  supports markdown formatting, so you'd be able to add links to your terms and
  conditions or other relevant information.
</Tip>

Additionally, you can also set up [dual-sided incentives](/help/article/dual-sided-incentives) for your users to offer discounts to their users who sign up via their referral link (e.g. "25% off for the first 3 months").

Once this is set up, your partners will be able to see the reward and discount in the [embedded referral dashboard](/docs/partners/embedded-referrals):

<Frame>
  <img alt="Dual-sided incentives" />
</Frame>

## Step 2: Listen to Dub webhooks and provision rewards to your partners

To provision the non-monetary rewards (credits, free months, etc.) to your partners, you would need to listen to Dub's [webhook events](/docs/webhooks/event-types) to detect when a new signup / sale is tracked.

Depending on the reward you're offering, you would need to listen to:

* [`lead.created`](/docs/webhooks/events/lead-created) - if you're offering a lead reward
* [`sale.created`](/docs/webhooks/events/sale-created) - if you're offering a sale reward

Then, within the webhook handler, you can update the partner's Stripe subscription to provision the free credits / months accordingly:

```ts title="app/api/webhooks/dub/lead-created/route.ts" theme={null}
import Stripe from "stripe";
// Install via npm i dub / yarn add dub / pnpm add dub
import { LeadCreatedEvent } from "dub/models/components";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function leadCreated(data: LeadCreatedEvent["data"]) {
  const { partner } = data;
  if (!partner?.tenantId) return;

  // Limit the number of free credits / months that a partner can receive (max 10 referrals)
  if (partner.totalLeads >= 10) return;

  // Get customer based on partner's tenant ID (getCustomerByTenantId is your own function)
  const customer = await getCustomerByTenantId(partner.tenantId);

  if (!customer?.stripeCustomerId) return;

  // Get the customer's active subscription (e.g. for metered usage / free months)
  // Get the customer's active subscription (e.g. for metered usage / free months)
  const subscriptions = await stripe.subscriptions.list({
    customer: customer.stripeCustomerId,
    status: "active",
    limit: 1,
  });

  const [subscription] = subscriptions.data;

  if (!subscription?.items.data[0]) return;

  // Option A: Add free credits via metered usage
  await stripe.subscriptionItems.createUsageRecord(
    subscription.items.data[0].id,
    { quantity: 500, timestamp: Math.floor(Date.now() / 1000) },
  );

  // Option B: Or extend the subscription (e.g. add 1 free month via promotion/coupon)
  // await stripe.subscriptions.update(subscription.id, {
  //   promotion_code: "promo_free_month_per_referral",
  // });
}
```

## Step 3: Graduating partners from non-monetary rewards to monetary rewards

Once a partner has referred a certain number of users, you can move them to a separate [partner group](/help/article/partner-groups) with monetary rewards (e.g. 30% commission per sale for 12 months).

There are two ways to do this:

1. [Manually move the partner](/help/article/partner-groups#moving-partners-between-groups) to the new group from the [partner details page](/help/article/managing-program-partners#viewing-partner-details) or via the [partner table](https://app.dub.co/program/partners).
2. Automatically move the partner to the new group via [group move rules](/help/article/partner-groups#group-move-rules) based on their performance metrics (e.g. total leads, total sales, total commissions, etc.).

<Frame>
  <img alt="Example group move settings" />
</Frame>


# Configuring SAML SSO with Okta
Source: https://dub.co/help/article/okta-saml

For Dub Enterprise users, you can securely manage your team's access to Dub using Okta SAML SSO.

<Tip>
  This feature is only available on [Dub Enterprise](https://dub.co/enterprise).
</Tip>

For Dub Enterprise users, you can securely manage your team's access to Dub using [Okta SAML SSO](https://support.okta.com/help/s/article/okta-saml).

## Step 1: Create SAML Integration

In your Okta Dashboard, click on **Applications** in the sidebar.

<Frame>
  <img alt="Applications tab on the Okta Dashboard" />
</Frame>

Then, click on **Create App Integration**.

<Frame>
  <img alt="Create App Integration button on the Okta Dashboard" />
</Frame>

Select **SAML 2.0** as the sign on method, and click **Next**.

<Frame>
  <img alt="Saml 2.0 option on the Okta Dashboard" />
</Frame>

In the **General Settings** page, enter "Dub" as the app name, and click **Next**.

<Frame>
  <img alt="General Settings page on the Okta Dashboard" />
</Frame>

Copy the following values and paste them into the **SAML Settings** > **General** section:

```text title="Single sign-on URL" theme={null}
https://api.dub.co/auth/saml/callback
```

```text title="Audience URI (SP Entity ID)" theme={null}
https://saml.dub.co
```

<Frame>
  <img alt="SAML Settings page on the Okta Dashboard" />
</Frame>

## Step 2: Configure Attribute Statements

Right below the **General** section, you'll see the **Attribute Statements** section.

<Frame>
  <img alt="Attribute Statements section on the Okta Dashboard" />
</Frame>

Click on **Add Row** and add the following attribute statements:

| Name        | Value            |
| ----------- | ---------------- |
| `id`        | `user.id`        |
| `email`     | `user.email`     |
| `firstName` | `user.firstName` |
| `lastName`  | `user.lastName`  |

Once that's done, click **Next**.

## Step 3: Submit Feedback

Under the **Feedback** section, select **This is an internal app we have created** and click **Finish**.

<Frame>
  <img alt="Feedback section on the Okta Dashboard" />
</Frame>

## Step 4: Copy the Metadata URL

After you've submitted the feedback, you'll be redirected to the **Sign On** tab, which contains the metadata URL.

<Frame>
  <img alt="Sign On tab on the Okta Dashboard" />
</Frame>

Copy the metadata URL and return to the Dub dashboard.

## Step 5: Configure SAML SSO on Dub

In your workspace settings, click on **Security** in the **Workspace** group.

<Frame>
  <img alt="Directory Sync section on the Dub Dashboard" />
</Frame>

Under the **SAML Single Sign-On** section, click on **Configure**. This will open up the SAML SSO modal:

1. Select **Okta** as the SAML provider.
2. Enter the Metadata URL value that you copied from Step 4.
3. Click **Save changes**.

<Frame>
  <img alt="SAML SSO Modal" />
</Frame>

## Step 6: Assign Users

<Warning>
  We highly recommend configuring [SCIM Directory Sync](/help/article/okta-scim)
  before assigning users & groups to your workspace. This will ensure that your
  users are automatically added to your workspace when they sign in for the
  first time, as well as automatically removed when they are deactivated in
  Okta.
</Warning>

Once you've configured SAML SSO, you can start assigning users & groups to your workspace.

Click on the **Assignments** tab, and click **Assign**. You can choose to assign users individually, or assign them in bulk by group.

<Frame>
  <img alt="Assigning users in Okta" />
</Frame>

Select the users or groups you want to assign and click **Assign**.

<Frame>
  <img alt="Assigning users in Okta choose users" />
</Frame>

In the next screen, scroll to the bottom and click **Save and Go Back**.

<Frame>
  <img alt="Saving Okta assignment" />
</Frame>

Your assigned users should now receive an invitation email to join your Dub workspace.

<Frame>
  <img alt="SAML invite email" />
</Frame>

They will also be able to sign in to Dub using Okta SSO.


# Configuring SCIM Directory Sync with Okta
Source: https://dub.co/help/article/okta-scim

For Dub Enterprise users, you can automatically provision and deprovision users from your Okta directory to Dub using SCIM Directory Sync.

<Tip>
  This feature is only available on [Dub Enterprise](https://dub.co/enterprise).
</Tip>

For Dub Enterprise users, you can automatically provision and deprovision users from your Okta directory to Dub using [SCIM Directory Sync](https://developer.okta.com/docs/concepts/scim/).

## Step 1: Configure Directory Sync on Dub

In your workspace settings, click on **Security** in the **Workspace** group.

<Frame>
  <img alt="Directory Sync section on the Dub Dashboard" />
</Frame>

Under the **Directory Sync** section, click on **Configure**. This will open up the Directory Sync modal:

1. Select **Okta** as the Directory Provider.
2. Click **Save changes**.

<Frame>
  <img alt="SCIM Modal" />
</Frame>

This will generate a Directory Sync connection for your Dub workspace, and return 2 values, which will be needed in Step 2:

1. **SCIM 2.0 Base URL**
2. **OAuth Bearer Token**

<Frame>
  <img alt="SCIM Modal Configured" />
</Frame>

## Step 2: Add Provisioning to SAML Application

Go to the **General** tab of your existing Dub Okta SAML application that you want to enable SCIM provisioning for. Under **App Settings** click **Edit**.

<Frame>
  <img alt="General tab of Okta SAML application" />
</Frame>

Select the "Enable SCIM provisioning" option and click **Save**.

<Frame>
  <img alt="Enable SCIM provisioning" />
</Frame>

You should now see a **Provisioning** tab. Select it and click **Edit**.

<Frame>
  <img alt="Provisioning tab of Okta SAML application" />
</Frame>

1. Under the **SCIM connector base URL** field, enter the **SCIM 2.0 Base URL** value from Step 1.

2. Under **Unique identifier field for users**, enter `userName`.

```text title="Copy this unique identifier" theme={null}
userName
```

3. Under **Supported provisioning actions**, select the following options:

* Push New Users
* Push Profile Updates
* Push groups

4. Under **Authentication Mode**, select **HTTP Header**.

5. Copy & paste the **OAuth Bearer Token** value from Step 1 into the **Authorization** field.

<Frame>
  <img alt="Configure SCIM provisioning" />
</Frame>

Click "Test Connector Configuration" to verify that the connection is working, and then click "Save".

## Step 3: Configure Provisioning Actions

Under the **To App** section, click **Edit**.

Check the following options, and click **Save**:

* Create Users
* Update User Attributes
* Deactivate Users

<Frame>
  <img alt="SCIM Provisioning Actions" />
</Frame>

## Step 4: Assign Users

Once you've configured Directory sync, you can assign users to Dub directly within Okta.

Click on the **Assignments** tab, and click **Assign**. You can choose to assign users individually, or assign them in bulk by group.

<Frame>
  <img alt="Assigning users in Okta" />
</Frame>

Select the users or groups you want to assign and click **Assign**.

<Frame>
  <img alt="Assigning users in Okta choose users" />
</Frame>

In the next screen, scroll to the bottom and click **Save and Go Back**.

<Frame>
  <img alt="Saving Okta assignment" />
</Frame>

Your assigned users should now receive an invitation email to join your Dub workspace.

<Frame>
  <img alt="SAML invite email" />
</Frame>

They will also be able to sign in to Dub using Okta SSO.


# How to pass query parameters in short links?
Source: https://dub.co/help/article/parameter-passing

Learn more about how Dub supports query parameter passing from your short links to their destination URLs.

Query parameters such as [UTM parameters](/help/article/utm-builder) are a fantastic way to understand how your marketing links are performing. Whether it is to understand which social media channels are driving the most traffic, or to understand which campaigns are the most effective, query parameters are a great way to do just that.

With Dub, you can [add UTM parameters](/help/article/utm-builder#how-to-use-the-dub-utm-builder) using our UTM builder before shortening them. This gives you the best of both worlds – the benefits of using UTMs but still have a short, brandable, and easily shareable links.

But what about *after* you have shortened your links? For that, you can leverage our parameter passing feature.

## How does parameter passing work?

With parameter passing, you can attach UTM parameters – or any other query parameter – to your short link and they're passed on to the destination URL.

For example, you can pass a `utm_source` parameter with the value `website` to a short link like [d.to/passthrough](https://d.to/passthrough) and they're passed on to the destination URL.

| Short link                                                                          | Destination URL                                                      |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| [d.to/passthrough](https://d.to/passthrough)                                        | `dub.co/changelog/pass-url-parameters?ref=dub`                       |
| [d.to/passthrough?utm\_source=website](https://d.to/passthrough?utm_source=website) | `dub.co/changelog/pass-url-parameters?ref=dub.co&utm_source=website` |

If there are any duplicate parameters, the parameter in the short link will override its counterpart in the destination URL:

| Short link                                                         | Destination URL                                   |
| ------------------------------------------------------------------ | ------------------------------------------------- |
| [d.to/passthrough](https://d.to/passthrough)                       | `dub.co/changelog/pass-url-parameters?ref=dub`    |
| [d.to/passthrough?ref=dub.co](https://d.to/passthrough?ref=dub.co) | `dub.co/changelog/pass-url-parameters?ref=dub.co` |

This is particularly helpful when you're posting the same short link across multiple social media channels and want to quickly customize the UTM parameters for each channel without having to create a new short link for each one.

## How much does parameter passing cost?

Other link management platforms like Bitly restrict the parameter passing feature to their [Enterprise tier](https://support.bitly.com/hc/en-us/articles/4416831519373-What-is-parameter-passing).

On Dub, however, **parameter passing is built into every single Dub link**. This means you can use parameter passing even on the free plan.


# Managing partner commissions
Source: https://dub.co/help/article/partner-commissions

Learn how partner commissions work on Dub, and how to create manual commissions or clawbacks (negative commissions).

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing).
</Tip>

## Where to find your program commissions?

When viewing your Partner Program navigation, click on the **Commissions** page, under the **Insights** group:

<Frame>
  <img alt="Commissions page within your dashboard" />
</Frame>

This opens up the commissions table view, where you can see a list of your historical commissions for your program:

<Frame>
  <img alt="Commissions page within your dashboard" />
</Frame>

| Column             | Data                                                                                                                  |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| Date               | Commission date                                                                                                       |
| Customer           | The customer associated with the lead or sale                                                                         |
| Partner            | The partner earning the commission                                                                                    |
| Group              | The group the partner belongs to                                                                                      |
| Type               | The commission type (custom, click, lead, or sale)                                                                    |
| Amount             | The sale amount, or single count for custom, click, or lead rewards                                                   |
| Commission         | The amount the partner will receive                                                                                   |
| Status             | Current status of the commission. [Learn more about statuses](/help/article/partner-commissions#commission-statuses). |
| Additional actions | Actions to edit the commission, and copy the invoice ID                                                               |

<Note>
  To analyze commission earnings over time, see [Commission
  analytics](/help/article/commission-analytics).
</Note>

## Commission statuses

When a commission is recorded on Dub, it goes through the following statuses:

* `Pending`: This is the initial status for a commission. Depending on your program's [holding period](/help/article/partner-payouts#payout-holding-period), commissions can stay in "pending" status anywhere from less than a day to 90 days.
* `Processed`: When a commission has been added to a payout (after any applicable [holding period](/help/article/partner-payouts#payout-holding-period)), its status will change from "pending" to "processed".
* `Paid`: When the payout containing the commission has been paid, the commission will be updated to "paid" status.
* `On Hold`: When a commission is paused due to a pending [risk event](/help/article/risk-monitoring). On-hold commissions cannot be added to a payout until the risk event is resolved.

If needed, you can also update the status for a given commission accordingly:

* `Refunded`: If the sale for the commission was refunded. This is usually automatically detected via our [Stripe integration](https://dub.co/integrations/stripe), or can also be manually set via the dashboard or the [API](/docs/api-reference/endpoint/update-a-commission).
* `Fraud`: If a commission event was found to be fraudulent, you can mark them as "Fraud" via the dashboard or the [API](/docs/api-reference/endpoint/update-a-commission). **This action cannot be undone.**
* `Canceled`: If a commission event was canceled/voided (e.g. in cases of self-referrals where commissions are not eligible for payout), you can mark them as "Canceled" via the dashboard or the [API](/docs/api-reference/endpoint/update-a-commission). **This action cannot be undone.**
* `Duplicate`: If a commission event was found to be a duplicate, you can mark them as "Duplicate" via the dashboard or the [API](/docs/api-reference/endpoint/update-a-commission). **This action cannot be undone.**

[Learn more about changing commissions statuses](/help/article/partner-commissions#commission-status).

## Single commission view

Each commission has its own page that provides further details about the commission, associated payout, and activity. This can be helpful when you need more details about a commission, or you want to know when something changed.

<Frame>
  <img alt="Single commission view" />
</Frame>

## Editing commissions

You can change a commission's status and edit the earnings amount for all unpaid commissions. This can be completed from one of two areas:

<Steps>
  <Step title="Commissions table">
    From the Commissions table, click the `⋯` button and select **Edit commission**.

    <Frame>
      <img alt="Edit commission table" />
    </Frame>
  </Step>

  <Step title="Single commission page">
    From the [single commission page](/help/article/partner-commissions#single-commission-view), click the **Edit** button in the top right.

    <Frame>
      <img alt="Single commission edit" />
    </Frame>
  </Step>
</Steps>

With the edit commission modal open, you can change the status and earnings amount of any commission that hasn't been paid yet.

<Frame>
  <img alt="Edit commission modal" />
</Frame>

If you change a commission's status to `Canceled` or `Fraud`, you're also able to mark all previous commissions for this customer-partner pair as that status too. This will also prevent the partner from earning future commissions from this customer.

<Frame>
  <img alt="Mark all commissions" />
</Frame>

### Earnings amount

You can only edit the earnings amount for [custom commissions](#one-time-commissions) that haven't been paid yet. If the earnings amount is changed, it will also appear in the activity log on the [commissions page](/help/article/partner-commissions#single-commission-view).

<Frame>
  <img alt="Edit commission earnings modal" />
</Frame>

### Bulk editing

You can bulk edit the status of multiple commissions at once by clicking the checkboxes next to the commissions you want to edit, then click **Edit commissions**.

<Frame>
  <img alt="Bulk edit commissions selection" />
</Frame>

From the modal, select the new status for the commissions and click **save**.

<Frame>
  <img alt="Bulk edit commissions editing" />
</Frame>

<Tip>
  You can only bulk edit the status of commissions that haven't been paid yet.
</Tip>

## Creating manual commissions

Occasionally, you might need to create a one-off or recurring commission to account for a missed commission or other reasons. On Dub, you can easily create those commissions via the dashboard, and they will be included in the next payout for that partner.

This can be accomplished by clicking **Create commission** from your **Commissions** page.

<Frame>
  <img alt="Create one-time commission" />
</Frame>

It can also be accomplished by clicking **Create commission** from an individual partner profile

<Frame>
  <img alt="Create one-time commission" />
</Frame>

### One-time commissions

Manual one-time commissions are often used when you want to send a thank-you or bonus, fix a missed commission due to a clerical or tracking error, or create a payout that isn’t tied to a customer, referral link, or transaction.

<Frame>
  <img alt="Create one-time commission" />
</Frame>

| Input            | Description                                                                                               | Required |
| ---------------- | --------------------------------------------------------------------------------------------------------- | -------- |
| Amount           | Commission amount                                                                                         | ✅        |
| Description      | Add a custom description                                                                                  | ❌        |
| Commission date  | Enable to add a custom date. If a custom date isn't added, it will auto-fill to the current date and time | ❌        |

<Note>
  One-time commissions are recorded as `custom` commissions, which are **not** subject to your program's [payout holding period](/help/article/partner-payouts#payout-holding-period). They become eligible for payout immediately at the next payout cycle. The same applies to [bounty commissions](/help/article/program-bounties) and `custom` commissions created via the [API](/docs/api-reference/commissions/create).
</Note>

### Lead commission

Manual lead commissions are one-time and based on your [reward settings](/help/article/partner-rewards), so the commission is automatically calculated. Use this for rewarding a partner for a qualified signup or referral. If your program doesn't have a lead reward, this option won't show.

<Frame>
  <img alt="Create one-time commission" />
</Frame>

| Input           | Description                                                                                                                            | Required |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| Referral link   | The referral link that the lead is attributed to                                                                                       | ✅        |
| Customer        | The customer associated with the lead. [Read more about adding a customer ](/help/article/partner-commissions#creating-a-new-customer) | ✅        |
| Lead event date | The date of the lead event. If no date is selected, it will auto-fill to the current date and time                                     | ❌        |
| Lead event name | The name of the lead event.                                                                                                            | ❌        |

### Recurring sale commission

Manual sale commissions are recurring and based on your [reward settings](/help/article/partner-rewards), so the partner commission is calculated automatically after entering the sale amount. Sale commissions also create a `sale event` under your analytics, and updates your revenue statistics.

<Frame>
  <img alt="Create sale commission" />
</Frame>

| Input         | Description                                                                                    | Required                                |
| ------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------- |
| Referral link | The referral link that the sale is attributed to                                               | ✅                                       |
| Customer      | The customer associated with the sale. Read more about adding a customer                       | ✅                                       |
| Event source  | Create a new sale event, or import from Stripe                                                 | ✅                                       |
| Sale date     | The date of the sale. If no date is selected, it will auto-fill with the current date and time | ❌                                       |
| Sale amount   | The amount of the sale in USD.                                                                 | Required if not reusing existing events |
| Invoice ID    | The invoice ID of the sale. Can be used to fetch the commission later via the API              | ❌                                       |
| Product ID    | The product ID of the sale. Can be used to fetch the commission later via the API              | ❌                                       |

### Creating a new customer

If the customer you need to associate the commission with doesn't exist, you can create one in Dub to match your records. To do this, when searching for a customer in the customer field, click on **Create new customer**.

This will open up a modal where you can add your customer's details or import from Stripe.

<Frame>
  <img alt="Create new customer location" />
</Frame>

Once the customer is created, you can then associate any commissions to them, and future commissions will automatically be associated with that customer.

<Frame>
  <img alt="Create customer modal" />
</Frame>

| Input              | Description                                                          | Required |
| ------------------ | -------------------------------------------------------------------- | -------- |
| Name               | The name of the customer                                             | ❌        |
| Email              | The customer's email                                                 | ❌        |
| Country            | The customer's country                                               | ✅        |
| External ID        | Your unique ID for this customer (e.g. database ID of email)         | ✅        |
| Stripe Customer ID | Add a Stripe customer ID to attribute recurring sales to the partner | ❌        |

#### Importing customers from Stripe

If you've already [connected to Stripe](/docs/integrations/stripe#stripe), you can import your customer by clicking on **Import from Stripe**. This will allow you to search existing Stripe customers by their email, and click to import their details into Dub.

<Tip>
  If a customer with the same Stripe Customer ID already exists in Dub, you
  won't be able to import that customer.
</Tip>

>

<Frame>
  <img alt="Importing a Stripe customer" />
</Frame>

Once selected, it will auto-fill the customer profile details. Click **Create customer** to add them to Dub.

<Frame>
  <img alt="Importing a Stripe customer" />
</Frame>

### Importing commissions from Stripe invoices

During the commission creation flow, you can choose between the following options:

1. **Create new events**: Create a new sale event entirely.
2. **Import from Stripe**: Create commissions from the customer's existing Stripe invoice payment events

<Frame>
  <img alt="Importing invoices from Stripe" />
</Frame>

Then, once you click **Create commission**, this will import the customer's paid invoices and record them as sale events + partner commissions to their [customer page](help/article/customer-insights).

## Creating a clawback

In the event an incorrect commission has been paid to a partner, you can create a clawback commission, which results in a negative commission for the partner and is subtracted from their next payout.

To create a clawback, click the **More button** next to **Create commissions**, then **Create clawback**.

<Frame>
  <img alt="Commissions dropdown" />
</Frame>

Fields required to create a clawback:

* **Partner**: The partner receiving the clawback
* **Amount**: The clawback amount
* **Clawback reason**: *Order canceled, Fraud, Terms violation, Tracking error, Payment failed, Ineligible partner, Duplicate commission, Other*

After the clawback is created, you'll see a negative commission in your table view:

<Frame>
  <img alt="Clawback created" />
</Frame>

The partner affected will see this reflected in their earnings view on their dashboard:

<Frame>
  <img alt="Clawback partner view" />
</Frame>

## Exporting commissions

To export your commissions, click the more button next to **Create commissions** then **Export as CSV**.

<Frame>
  <img alt="Commissions dropdown with export option" />
</Frame>

In the **Export commissions** modal, select which fields you want to include in your export, then click **Export commissions** to initiate. An email will be sent to you with a link to download the CSV file.

<Frame>
  <img alt="Commission export options modal" />
</Frame>

<Tip>
  To export commissions with any filters you currently have applied, make sure to toggle on "Apply current filters". This will ensure that only the commissions matching your current filters will be included in the export.
</Tip>

After receiving the email, click **Download export** to download your commissions CSV file.

<Frame>
  <img alt="Commission export email" />
</Frame>


# Managing your partners with groups
Source: https://dub.co/help/article/partner-groups

Learn how you can create partner groups to segment partners by rewards, discounts, performance, location, and more.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Partner groups help you organize your program so it runs more efficiently. You can segment partners by rewards, discounts, performance, location, and more, making it easier to tailor incentives and track results.

You can also filter your [program analytics](/help/article/program-analytics#filtering-your-data) and [commission analytics](/help/article/commission-analytics#filtering-commission-analytics) by partner group, or use [partner tags](/help/article/partner-tags) for more flexible labeling.

<Frame>
  <img alt="Partner Groups" />
</Frame>

## What types of partner groups can I create?

Here are some examples of partner groups you can create on Dub:

**By persona**

* **Agencies** – Marketing or creative agencies promoting your product for their clients.
* **Content creators** – Bloggers, podcasters, or YouTubers producing relevant content.
* **Media publishers** – Websites, magazines, or networks with aligned audiences.

**By vertical**

* **Students** – University clubs, campus ambassadors, or student-led initiatives.
* **Finance** – Partners in banking, fintech, or investment sectors.
* **Tech** – Developers, SaaS providers, or technology-focused communities.

**By performance tier**

* **Rising stars** – New partners showing strong growth potential.
* **Top performers** – Consistently exceeding sales or referral targets.
* **Elite partners** – Highest earners driving significant revenue and strategic wins.

## Where can I view my partner groups?

In your **Partner Program** menu, click **Groups** in the Partners section to view your partner groups

<Frame>
  <img alt="Group navigation" />
</Frame>

## What is the default partner group?

Your program will always have a group labelled `Default Group` and it can be renamed at any time.

<Frame>
  <img alt="Default group" />
</Frame>

<Info>
  You cannot delete your default partner group. This group will also be the
  group that your partners will be reassigned to whenever you delete another
  partner group.
</Info>

## Creating a group

To create a group, click **Create group** in the top right corner or press `C` on your keyboard.

<Frame>
  <img alt="Create group" />
</Frame>

| Field       | Description                                                                                                                                                                                                                                                                                                  |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Group name  | Add a name to your group to help easily identify it later. <br /> <br /> **This name is for internal use only and is never visible to your partners.**                                                                                                                                                       |
| Group color | Set the color of the group with our simple color picker.                                                                                                                                                                                                                                                     |
| Group slug  | Each partner group uses a unique application landing page, which you can use to share with partners you want to automatically add to that group when they apply. <br /> <br /> **This name is for internal and external use**, so anyone you share the application link with can see the slug name provided. |
| Group ID    | This non-editable item is used for embedding the Dub white-labeled referral dashboard within your app. [Learn more](/docs/partners/embedded-referrals).                                                                                                                                                      |

## Group rewards

Every partner group can have one of each of the following:

| Reward type                 | Description                                                                                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Click-based (pay-per-click) | Rewarding partners for driving clicks to your product – similar to how Google Ads' pay-per-click works.                                            |
| Lead-based (pay-per-lead)   | A more effective alternative to pay-per-click, where you reward partners for leads generated (e.g. demo requests, free trial signups, etc.)        |
| Sale-based (pay-per-sale)   | This is the most common reward type, where you reward partners for driving sales to your product either via a rev-share or fixed-price commission. |

<Frame>
  <img alt="Group rewards" />
</Frame>

To create a reward, click **Create** on any of the reward types. Learn more about [creating rewards here](/help/article/partner-rewards).

If you want to duplicate your default group rewards on other groups, click **Duplicate default group** to copy over the parameters to save time.

## Group discounts

You can also create dual-sided incentives for your affiliate/referral programs, which give special discounts to customers who sign up via a referral link.
Some examples include:

* 25% discount for the first 12 months
* 30% lifetime discount
* \$50 one-off discount

This can drive powerful word-of-mouth growth as partners are more likely to share their link if it gives their audience/user base additional discounts, and on the other hand, their users are more likely to click on their links as well if they're getting a special deal.

<Frame>
  <img alt="Group discounts" />
</Frame>

You can create only one discount per group. To create a discount, click **Create** on the discount block.

Learn more about [creating discounts here](/help/article/dual-sided-incentives).

## Group settings

Each group has individual settings that allow you to customize the name and additional settings.

<Frame>
  <img alt="Group settings" />
</Frame>

### Group name

The name of the partner group can be changed at any time. It's for internal use only and is never visible to your partners.

### Group slug

By default, the group slug will auto-generate based on the group name, but you're able to change this at any time. For program landing pages and internal group page URLs.

<Tip>The default group slug cannot be changed.</Tip>

### Group ID

When setting up the [Dub embedded referral dashboard](/docs/partners/embedded-referrals), you'll use this group ID. The ID cannot be changed.

### Payout holding period

Specify how long your commissions will be held in "pending" status before becoming eligible for payout. [Learn more.](/help/article/partner-payouts#payout-holding-period)

### Auto-approve

Enable or disable auto-approving new applicants to this group. [Learn more](/help/article/program-applications#auto-approve).

### Group move rules

<Tip>
  Group move rules are only available on [Advanced plan and
  above](https://dub.co/pricing/partners).
</Tip>

Group move rules lets you automatically move partners into a selected group when they meet specific criteria. This is useful when you want partners to earn higher rewards as they progress in your program, without needing to manually move them from one group to another.

For instance, you can move partners to the `VIP Group` once their total commissions reach \$100:

<Frame>
  <img alt="Example group move settings" />
</Frame>

You can also include/exclude certain groups from being subject to the move rule by adding an optional group condition – e.g. partners must be in the `Publisher` group to be automatically moved to the `VIP Group` when their total commissions reach \$100:

<Frame>
  <img alt="Group move rule condition" />
</Frame>

After adding your performance condition, click **Add condition**, then choose whether the partner group **is**, **is not**, **is one of**, or **is not one of** the selected group or groups. The partner must meet both the performance criteria and this group condition to be moved.

## Adding partners to a group

There are many ways to add partners to a group, depending on whether they're already enrolled or haven't joined your program yet.

### Inviting partners to apply

After your group is created, when you share the unique application URL, all new applicants who applied through that page will be assigned to that group.

<Frame>
  <img alt="Applications table" />
</Frame>

### Approving applications

When reviewing new applications, you'll be asked to confirm their assigned group, and you can also change their assigned group if you need to.

<Frame>
  <img alt="Select group" />
</Frame>

### Adding multiple partners to a group

When [viewing all your partners](http://app.dub.co/program/partners), you can use the multi-select function to add one or many partners at a time to any group.

Simply click the checkbox beside their name and click **Add to group** to move them to your desired partner group.

<Frame>
  <img alt="All partners" />
</Frame>

## Moving partners between groups

There are a few ways to move partners between groups:

**Via the partner details page**

You can move a partner to a new group from their [partner details page](/help/article/managing-program-partners#viewing-partner-details):

<Frame>
  <img alt="Partner profile page group" />
</Frame>

Anytime you're changing a partner's group, you can enable **keep partner in selected group** to ignore any group move rules. This setting is also available from the partner's [advanced settings](/help/article/managing-program-partners#advanced-settings).

<Frame>
  <img alt="Partner move modal" />
</Frame>

**Via the partner table**

As mentioned above, you can also use the multi-select function in the [partners table](http://app.dub.co/program/partners) to move one or many partners at a time to any group.

Simply click the checkbox beside their name and click **Add to group** to move them to your desired partner group.

<Frame>
  <img alt="All partners" />
</Frame>

**Via group move rules**

You can also automatically move partners to a new group based on their performance metrics. This is useful when you want partners to earn higher rewards as they progress in your program, without needing to manage groups manually.

<Frame>
  <img alt="Group move rules example" />
</Frame>

Partners that have [ignore group move rules](/help/article/managing-program-partners#ignore-group-move-rules) enabled will stay within their current group and not be affected by any move rules.

### Partner notification emails

When partners are moved to another group, they'll receive an email like the one below, informing them of any reward and bounty changes associated with the move.

<Frame>
  <img alt="Group move email" />
</Frame>

### Group move logs

For transparency and auditability, we also keep a history of all group moves for each partner – both from manual moves and via group move rules:

<Frame>
  <img alt="Partner profile page group history" />
</Frame>

This gives you a complete history of a given partner's group membership changes, which can affect their [rewards](/help/article/partner-rewards), [discounts](/help/article/dual-sided-incentives), and [referral links](/help/article/partner-link-settings).

## How many partner groups can I create?

All Dub Partners customers can create partner groups, with the number allowed based on your plan.

| Plan                                        | Partner groups                           |
| ------------------------------------------- | ---------------------------------------- |
| [Business](https://dub.co/pricing/partners) | Up to 3 groups (including Default group) |
| [Advanced](https://dub.co/pricing/partners) | Unlimited groups                         |
| [Enterprise](https://dub.co/enterprise)     | Unlimited groups                         |


# Configuring referral links for your partner groups
Source: https://dub.co/help/article/partner-link-settings

Learn how set default referral links, additional links, and UTM parameters for each partner group in your Dub partner program

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

## Where to find your referral link settings

Each [partner group](/help/article/partner-groups) has its own referral link settings. To find them, open a group and click **Links** in the top navigation.

<Frame>
  <img alt="Group referral link settings" />
</Frame>

## Default referral links

A default link is created during program setup, so new [partner groups](/help/article/partner-groups) always have a referral link. You can also set up additional default links that are automatically created when a partner is enrolled in the partner group.

Click **Create Link** to add more default links:

<Frame>
  <img alt="Group default links" />
</Frame>

<Info>
  When creating a default link, you'll be asked to enter a **Destination URL**.
  This is the page users land on after clicking a referral link. It’s often your
  homepage or a dedicated program landing page.
</Info>

**Default Link Behavior**

* **When you create a default link**, a new link is generated for every partner in that group. This includes both current and future partners in that group.
* **When you update a default link's** destination URL, it updates all the corresponding partner links in that group with the destination URL you set.
* **When you delete a default link**, the link remains for existing partners in that group to ensure historical stats/commissions are preserved.

## Additional partner links

You can also let your partners create their own links beyond the defaults:

<Frame>
  <img alt="Additional partner links" />
</Frame>

For your additional partner links, you can specify the following:

* **Link limit**: the maximum number of links partners can create
* **Link domains**: the domains that your partner's links must match (either any page on that domain, or the homepage only)

<Frame>
  <img alt="Adding link domain" />
</Frame>

<Warning>
  Ensure the domain you're adding has conversion tracking set up, or your links
  won't be tracking correctly. [Learn more here.](/docs/quickstart/server)
</Warning>

## Link settings

Define the link structure and UTM parameters for all partners in the group.

<Frame>
  <img alt="Adding link domain" />
</Frame>

### Link structure

Dub supports two referral link formats. Choose based on how you want links to look and where they’ll be shared. Both formats work the same for attribution.

<Frame>
  <img alt="Link structure" />
</Frame>

<Info>
  You can use any of the referral link types interchangeably – the option above
  is to determine which format is shown in the partner dashboard.
</Info>

**Option 1: Short Links**

**Format:** `getacme.link/{partnerName}`

A clean, branded short link that’s easy to remember and perfect for sharing in social posts, bios, or messages.

<Tip>
  We recommend using short links for several reasons:

  1. Some web browsers block [client-side click tracking](/docs/sdks/client-side/features/click-tracking), which breaks attribution – with `getacme.link/john` the click will be tracked server-side too, which improves attribution accuracy
  2. Some web browsers drop query params, which breaks attribution as well
  3. It's cleaner ✨
</Tip>

**Option 2: Query Parameter**

**Format:** `acme.com?via={partnerName}`

Appends the partner name as a query string on your destination URL. Ideal for users who are [migrating from Rewardful](/help/article/migrating-from-rewardful) and want to maintain backwards compatibility for existing partner links.

Query parameter links do not carry UTM parameters. Because the partner key is already appended as a query string on your destination URL, group UTM settings are not applied with this link structure. Use [**Short Links**](/help/article/partner-link-settings#option-1-short-links) if you want default UTM tags on partner referral links.

### UTM parameters

Set default [UTM parameters](/help/article/utm-builder) for all links in this group. This gives you group-specific insights in analytics.

<Frame>
  <img alt="Link UTM" />
</Frame>

When you update a group’s [UTM template](/help/article/how-to-create-utm-templates), all partner links in the
group are refreshed and overwritten with the new UTM tags.

These settings only apply when the group uses the [**Short Links** structure](/help/article/partner-link-settings#option-1-short-links). They are not applied when [**Query Parameter**](/help/article/partner-link-settings#option-2-query-parameter) is selected.

## Changing your referral link domain

If you need to change the domain used for your referral links (for example, from `ref.getacme.com` to `ref.acme.com`), you must follow the steps below in order. Skipping or reordering these steps will break your existing partner links.

<Steps>
  <Step title="Add your new domain in Dub">
    Add your new domain (`ref.acme.com`) in your [domains settings page](https://app.dub.co/settings/domains).
  </Step>

  <Step title="Set up a redirect on your old domain">
    Set up a redirect from your old domain to your new domain so that any traffic hitting an existing partner link is forwarded to the new domain. You'll want a wildcard redirect that maps `ref.getacme.com/:path*` to `ref.acme.com/:path*`.

    Most hosting providers and CDNs support wildcard redirects. Check your provider's documentation for how to configure them (often via a configuration file, dashboard setting, or rewrite/redirect rule). If you're on [Vercel](https://vercel.com/docs/routing/redirects/configuration-redirects), you can add this to your `vercel.json` file.
  </Step>

  <Step title="Update your default link">
    Once both the new domain and the redirect are in place, update your **Default Link** to use the new domain on your [group links page](https://app.dub.co/program/groups/default/links).
  </Step>
</Steps>

## How partner group changes affect referral links

As mentioned earlier, a partner's link settings are tied to the [partner group](/help/article/partner-groups) they're in. When you update a partner's group, or delete groups, their partner links will be updated as well.

### Moving partners to a group

When a partner is moved to a new group, their links automatically update to match the new group’s defaults and UTM parameters.

<Info>
  **Why would you move partners?**

  * To promote them from a starter group to a group with a higher reward tier
  * To try out new rewards in a test group
  * To tidy up your structure and general reorganization
</Info>

### Deleting groups

If you delete a group, all partners in that group are moved to the default group. Their links are updated to match the default group’s settings.

<Info>
  **Why would you delete a group?**

  * The campaign has ended
  * You created a duplicate group by mistake
  * A beta trial group is no longer needed
</Info>


# Setting business details in partner payout invoices
Source: https://dub.co/help/article/partner-payout-invoice

Learn how to set custom business details (company name, address, tax ID) in your partner payout invoices.

Every time you [receive a payout](/help/article/receiving-payouts) on Dub, our platform automatically generates an invoice for accounting purposes.

With Dub, you can also set your business details (company name, address, tax ID) in your payout invoices for tax compliance.

<Steps>
  <Step title="Open the payout settings modal">
    First, go to your [payout settings page](https://partners.dub.co/settings/payouts) and click on the **Payout settings** button:

    <Frame>
      <img alt="partner invoice settings button" />
    </Frame>
  </Step>

  <Step title="Enter your business details">
    This will open up the Payout settings modal, where you can set your business name, address, and tax ID:

    <Frame>
      <img alt="partner invoice settings" />
    </Frame>
  </Step>

  <Step title="Save changes and view updated invoice">
    Once the changes are saved, they are automatically reflected in your payout invoices:

    <Frame>
      <img alt="partner invoice example" />
    </Frame>
  </Step>
</Steps>


# Sending partner payouts on Dub
Source: https://dub.co/help/article/partner-payouts

Learn more about how you can send payouts to your affiliate partners globally with Dub.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub Partners supports one-click (or fully automated) payouts to your partners around the world, [saving you upwards of 40+ hours a month](https://dub.co/customers/tella).

<Frame>
  <video />
</Frame>

If you're running a partner program with hundreds of partners, you can avoid manually sending each payout or [having to worry about payout failures](https://dub.co/blog/dub-vs-rewardful#partner-payouts). Dub lets you pay partners in one click, with tax compliance built in.

## Supported payout countries

Here's the full list of countries we support:

<PayoutSupportedCountries />

## Tax compliance

Dub Partners automatically handles tax compliance for your US-based partners by collecting [W-9 forms](https://support.stripe.com/express/questions/what-is-a-w-9-form) and sending out [1099-NEC forms](https://support.stripe.com/questions/intro-to-1099-nec-tax-forms-for-platforms-and-marketplaces) to individuals or entities that received \$600 or more in payments for a given fiscal year.

For non-US partners, Dub collects [W-8 forms](https://support.stripe.com/express/questions/what-is-a-w-8-form) to confirm they are not a US resident for tax purposes.

## Payouts dashboard

To view your payouts, go to your **Partner Program** tab and click on the **Payouts** page.

<Frame>
  <img alt="Partner payouts tab" />
</Frame>

At the top of the page, you'll see a summary of your pending and completed payouts:

<Frame>
  <img alt="Payout summary header" />
</Frame>

**Pending payouts**

This shows the total amount of commissions that are eligible for payment.

Hovering over the total will show you:

* **Eligible payouts** – due commissions for partners that have [configured their payout method](/help/article/receiving-payouts)).
* **Ineligible payouts** – due commissions for partners that have not [configured their payout methods yet](/help/article/receiving-payouts)).

Once you have at least one pending payout on Dub, you can click the **Confirm payouts** button to [review and confirm them](/help/article/partner-payouts#how-to-pay-your-partners).

**Total paid**

This shows the total amount of all commissions you've paid out to your partners.

Hovering over the total will show you:

* **Completed payouts** - Payouts that have been sent and confirmed
* **Processing payouts** - Payouts that are in progress (timing may vary based on the [selected payout method](/help/article/partner-payouts#payout-fees-and-timing))

Click **View invoices** to access past payout invoices.

In the payout table below, you can track and manage your payouts, confirm pending ones, and review past activity.

<Frame>
  <img alt="Payouts table" />
</Frame>

| Column  | Description                                                                                                                 |
| ------- | --------------------------------------------------------------------------------------------------------------------------- |
| Period  | The payout range, starting from the date of the partner’s first commission to the end of the month of their last commission |
| Partner | The partner receiving the payout                                                                                            |
| Status  | The current payout [status](/help/article/partner-payouts#payout-statuses)                                                  |
| Paid    | The date the payout was confirmed, and by whom                                                                              |
| Amount  | The total amount paid to the partner                                                                                        |

## Payout statuses

A payout can have one of the following statuses:

| Status     | Description                                                                                                                                                                                                                                                                                            |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Pending    | Payouts that have not been processed yet, which can either be currently due or remain in pending status (in cases where the partner has not [configured their payout method](/help/article/receiving-payouts), or the payout has not yet reached the [minimum payout amount](#minimum-payout-amount)). |
| Processing | Payouts that have been [confirmed](#how-to-pay-your-partners) – depending on your [payout method](#payout-fees-and-timing), this step can take up to 4 business days.                                                                                                                                  |
| Sent       | When the funds have been sent to the partner's connected bank account.                                                                                                                                                                                                                                 |
| Completed  | When the payout has been fully paid out to the partner.                                                                                                                                                                                                                                                |
| Failed     | This is an edge case that happens with PayPal payouts, where the partner's PayPal is not set up to receive payouts. No action is required from your end in these cases – the partner will need to sort out their PayPal account and retry the payout from their end.                                   |
| Canceled   | For payouts that have been canceled – usually when a partner is banned from the program.                                                                                                                                                                                                               |

## Payout holding period

On Dub, you can also set a "payout holding period" to specify how long your commissions will be held in "pending" status before becoming eligible for payout.

This is useful for safeguarding against potential refunds, chargebacks, or fraudulent transactions.

<Info>
  If you're using our [Stripe integration](/docs/integrations/stripe), any
  refunds that happen on Stripe will be automatically detected by Dub and the
  [corresponding commission will also be marked as
  refunded](/help/article/partner-commissions#commission-statuses).
</Info>

To set a payout holding period for your program, go to your [individual groups](/help/article/partner-groups) and click on the **Settings** tab. Here you'll find the payout holding period settings in the "Addtional settings" group.

<Frame>
  <img alt="Program payout settings" />
</Frame>

Your options include the following:

* 0 days (default)
* 7 days
* 14 days
* 30 days
* 60 days
* 90 days

When you update the payout holding period, you can apply the new setting to every group at once. Just select the box labelled `Apply to all groups` before confirming. The change will take effect right away.

<Frame>
  <img alt="payout holding period" />
</Frame>

<Note>
  The payout holding period only applies to `click`, `lead`, and `sale` commissions – it does **not** apply to `custom` commissions.

  This means that [bounty commissions](/help/article/program-bounties) and [manually created one-time commissions](/help/article/partner-commissions#one-time-commissions) (which are both recorded as `custom` commissions) become eligible for payout immediately at the next payout cycle, regardless of your group's holding period. This applies whether the commission is created via the dashboard or the [API](/docs/api-reference/commissions/create).
</Note>

## Minimum payout amount

You can also set an optional "minimum payout amount" (e.g. \$100). When this is set, partners with earnings below the minimum payout amount won't be eligible for payout until they reach the specific threshold set.

Click the button in the top right to show the **Payout settings**. Unlike the payout holding period, this setting is program-wide.

<Frame>
  <img alt="Payout settings button" />
</Frame>

Your options include the following:

* \$0 (default)
* \$10
* \$20
* \$50
* \$100

<Frame>
  <img alt="Payout settings" />
</Frame>

## How to pay your partners

When one or more of your partners have earned more than the minimum payout amount, click **Confirm payouts** from the [summary header](/help/article/partner-payouts#summary-header) to open the payout details.

* **Payout method:** Choose your payout method based on the accounts you’ve connected. Click the gear icon to manage [connected payment methods](https://app.dub.co/settings/billing).
* **Cutoff Period:** If set, only commissions earned on or before this date will be paid. Useful for reconciliation payouts for a specific fiscal period (e.g. last month, last quarter) without including newer commissions.

If everything looks right, click **Confirm payout** to trigger a new payout to your partners.

<Frame>
  <video />
</Frame>

### Selecting payouts

When confirming payouts, it will include all pending payouts, but there are times you only want to payout certain partners.

To do this, select the `Pending` status payouts and click **Confirm selected** at the top of the table. This will allow to review andconfirm payouts for the selected partners only.

<Frame>
  <img alt="Multi-selecting partners for payout" />
</Frame>

### Excluding payouts

If you need to exclude specific partners when confirming payouts, hover over the partner row in the payouts table and click **Exclude**. This excludes the partner from the current payout batch.

<Frame>
  <img alt="Excluding payouts" />
</Frame>

If you need to include the partner again, hover over their row and click **Include**.

## Payout fees and timing

On Dub, you can send payouts using various payment methods such as ACH, credit card, and more. Depending on your Dub plan and the payment method you choose, your payout fees and timing will vary:

| Payment method                                  | Fee                                                                                                  | Timing                |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- |
| [ACH](/help/article/how-to-set-up-bank-account) | [Business / Advanced](https://dub.co/pricing): 5% <br /> [Enterprise](https://dub.co/enterprise): 3% | Up to 4 business days |
| Credit card                                     | [Business / Advanced](https://dub.co/pricing): 8% <br /> [Enterprise](https://dub.co/enterprise): 6% | Instant               |

For larger payouts, we recommend using ACH as it is the most cost-effective option. Here's [how you can set up your bank account](/help/article/how-to-set-up-bank-account) for ACH payouts.


# Customizing your partner profile
Source: https://dub.co/help/article/partner-profile

Create a strong partner profile to build trust, credibility, and increase partnership opportunities on Dub.

Having a strong partner profile is key to getting accepted into partner programs. Follow this guide for help and tips to make your profile stand out.

## Where to find your partner profile

From your main navigation, click the **Partner Profile** icon. Here you can find the fields to add more details about yourself, your interests, and how you earn online.

<Frame>
  ![Partner profile location](https://assets.dub.co/cms/partner-profile-location-1.png)
</Frame>

## Profile Details

Your entire partner profile is viewable by all enrolled programs, and when you submit your application, make sure it looks great and stands out!

<Frame>
  ![Profile details](https://assets.dub.co/cms/profile-details.png)
</Frame>

### Basic information

The section is for your core details and information that's required to set up your Dub account.

#### Profile photo

Upload a clear, professional or on-brand image. Using a profile image or a recognized logo helps build trust. It's recommended to upload a JPG or PNG that's at least 160px x 160px to fill the profile image.

#### Full name

Input your first and last name so that program owners can easily find you within their program. Some program owners might search for you via your name, so it makes it easier to find your profile quickly in their program.

#### Email

Your email address is used for all partner communications. So when you get paid out or you get accepted into other programs, this email will be used to receive those notifications.

#### Location

This helps if programs are geo-specific and consider that during the application process, or want to understand your audience reach.

<Warning>
  To change your country, please [reach out to support](https://dub.co/contact/support) regardless of whether you've received payouts on Dub – country changes are handled by our team for compliance reasons.
</Warning>

#### Profile Type

During your initial account setup, you would've selected one of the following options:

* Individual
* Company

If you are listed as a company, you'll also need to provide your company name. This information is shown to the program owners.

<Frame>
  ![Partner type](https://assets.dub.co/cms/partner-profile-company.png)
</Frame>

### Identity verification

To verify your identity, you'll need to upload a valid government-issued picture ID. This helps programs ensure you're a real person and not a bot.

<Frame>
  <img alt="Identity verification" />
</Frame>

During verification, you'll also be asked to take a live selfie to match your picture ID. The process takes less than 30 seconds and helps build trust on the network.

### Website and socials

Add and verify your website and social accounts to prove ownership and build trust with programs.

This helps programs understand your presence and where you’re active, so they can evaluate your fit for their program.

<Frame>
  ![Partner type](https://assets.dub.co/cms/profile-websitesocials.png)
</Frame>

You can verify one of each:

#### Website

To verify your website, you'll need to add a TXT record to your domain's DNS settings.

<Frame>
  <img alt="Website verification" />
</Frame>

#### YouTube

To verify your YouTube profile, you'll need to add the unique code provided to your YouTube channel's English description temporarily. Once the code has been added to your profile, click **Verify**. After the verification is complete, you can remove the temporary code from your description.

<Frame>
  <img alt="YouTube verification" />
</Frame>

Navigate to your [YouTube Studio](https://studio.youtube.com) dashboard, and click on `Customization`, and scroll to `Description`.

<Frame>
  <img alt="YouTube description" />
</Frame>

**If your channel's main description is in English**, add the code temporarily to that description, save your changes, then verify in Dub.

<Frame>
  <img alt="YouTube english description" />
</Frame>

**If your channel's main description is not in English**, add the code temporarily to your English description, save your changes, then verify in Dub.

<Frame>
  <img alt="YouTube non-english verification" />
</Frame>

If you don't have an English description, you may need to create one first.

<Frame>
  <img alt="YouTube non-english adding" />
</Frame>

#### Instagram

To verify your Instagram profile, you'll need to add the unique code provided to your Instagram bio temporarily. Once the code has been added to your profile, click **Verify**. After the verifications is complete, you can remove the temporary code from your profile.

<Frame>
  <img alt="Instagram verification" />
</Frame>

#### X (Twitter)

To verify your X profile, enter your username and click **Verify**. This will open the X app authorization for Dub. Click authorize to verify your account, and you'll redirect back to your profile.

#### TikTok

To verify your TikTok profile, enter your username and click **Verify**. This will open the TikTok app authorization for Dub. Click authorize to verify your account, and you'll redirect back to your profile.

#### LinkedIn

To verify your LinkedIn profile, you'll need to add the unique code provided to a new LinkedIn post. Once that post is live, paste the URL of that post into the verification field and click **Verify account**.

You can only verify personal LinkedIn profiles, not company pages.

<Frame>
  <img alt="LinkedIn verification" />
</Frame>

## About you and your expertise

Consider this section to be your pitch when program owners are deciding who to accept into their program. A well-written profile, with aligned interests, helps you stand out and get accepted into more programs.

<Frame>
  ![About you and your expertise](https://assets.dub.co/cms/profile-expertise.png)
</Frame>

### About you

Craft a strong bio to help you stand out alongside other applicants. Your bio should clearly explain who you are, what you do, and who your audience is. For example: 

> “I’m a creator who has a large audience across YouTube, TikTok, and email newsletters. I focus on product reviews, how-to videos, and educational content. My audience is mostly US-based and interested in finance and tech, with over 2 million monthly views across my channels.” 

This gives program owners a quick sense of your reach and focus area.

You can also share any results that prove your ability to drive impact. You might mention how many signups, clicks, or sales you’ve generated for past brands. You can also highlight which industries you’ve worked with and the types of content that tend to perform best for you. For example: 

> “I’ve driven over 2,500 signups for SaaS products in the past three months. I’ve worked with brands in DTC, health & wellness, and crypto, and I’ve seen the strongest results from short-form UGC, newsletter placements, and search ads.”

<Tip>
  Make your bio brief and to the point. Consider your reader and respect their time.
</Tip>

### Industry interests

Add the industries you care about and post content about. This helps programs ensure your content is aligned with theirs and allows them to discover you as well. You can select up to eight industries.

<Frame>
  ![Industry interests](https://assets.dub.co/cms/profile-interests-1.png)
</Frame>

Click **Edit interests** to add or remove industries.

<Frame>
  ![Industry interests](https://assets.dub.co/cms/profile-interests.png)
</Frame>

### Estimated monthly traffic

Provide your best estimate of your actual total monthly traffic, including your websites, newsletters, and social accounts. You can select from:

* 0 - 1,000
* 1,000 - 10,000
* 10,000 - 50,000
* 50,000 - 100,000
* 100,000+

## How you work

Share how you prefer to earn and promote products to help programs understand your style of partnership.

<Frame>
  ![How you work](https://assets.dub.co/cms/profile-howyouwork.png)
</Frame>

### Preferred earning structure

Choose how you prefer to be rewarded, and select all that apply. Select from:

| Structure             | Example                                           |
| --------------------- | ------------------------------------------------- |
| Rev-share (% of sale) | Earn 20% of the sale value                        |
| Per sale (CPS)        | Earn \$20 for each sale you refer                 |
| Per lead (CPL)        | Earn \$5 for every qualified lead you send        |
| Per click (CPC)       | Earn \$0.50 for every click on your referral link |
| One-time payment      | Earn \$50 one-time for each sale referred         |

### Sales channels

Provide the channel where you intend to share your referral links, and select all that apply. Select from:

* Blogs
* Direct selling
* Social media
* Company referrals
* Coupons
* Newsletters
* Events


# Partner referrals
Source: https://dub.co/help/article/partner-referrals

Learn how to reward partners when they refer other partners to your program.

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

With Dub's Partner Referral feature, you can reward existing partners for referring other **influencers, affiliates, and publishers** to join your partner program.

This is exceptionally useful when your best partners already know other high quality partners who would be a good fit for your program.

## How partner referrals work

Partner referrals lets you reward partners when another partner they refer to your program meets a certain condition.

When a prospective partner visits your program application page, Dub automatically tracks where they came from (e.g. via your footer link, Google search, etc.) and displays these stats in your [Application Analytics](/help/article/application-analytics) dashboard.

<Frame>
  <img alt="Application analytics funnel chart" />
</Frame>

Similarly, if the referred partner visits your program application page through an existing partner's referral link, Dub tracks and attributes the application to the referring partner.

## Creating a partner referral reward

In your [group rewards](https://app.dub.co/program/groups/default/rewards) tab, you'll see the "Partner Referral" reward type. Click **Create** to set up a partner referral reward for that [partner group](https://dub.co/help/article/partner-groups).

<Frame>
  <img alt="Group reward view" />
</Frame>

From the referral reward creation view, you can select the reward type between [percentage-based](#percentage-based-rewards) or [flat-fee](#flat-fee-rewards) – the differences between the two are explained below.

<Frame>
  <img alt="Default reward view" />
</Frame>

## Flat fee rewards

For flat fee rewards, you can set a fixed one-time amount that the referring partner will earn when the referred partner meets a condition, such as:

| Condition          | Description                                                                                                      |
| ------------------ | ---------------------------------------------------------------------------------------------------------------- |
| **Is approved**    | The referring partner earns a reward when their referred partner is approved to your program.                    |
| **Earns at least** | The referring partner earns a reward when their referred partner earns at least a certain amount in commissions. |

### When the referred partner is approved to your program

When you select the **is approved** option, the referring partner will earn a fixed reward amount when the referred partner is approved to your program.

<Frame>
  <img alt="Referral reward is approved" />
</Frame>

### When the referred partner earns at least a certain amount in commissions

When you select the **earns at least** option, the referring partner will earn a fixed reward amount when the referred partner earns at least a certain amount in commissions.

For example, if you set the condition to **earns at least** \$100 and the reward amount to \$20, the referring partner will earn a \$20 referral reward when the referred partner earns at least \$100 in commissions.

<Frame>
  <img alt="Referral reward earns at least" />
</Frame>

## Percentage-based rewards

For percentage-based rewards, you can select between two options:

| Condition                         | Description                                                                              |
| --------------------------------- | ---------------------------------------------------------------------------------------- |
| **Original sale amount**          | The referring partner earns a percentage of the referred partner's original sale amount. |
| **Referred partner's commission** | The referring partner earns a percentage of the referred partner's commission.           |

### Original sale amount

When you select the **original sale amount** option, the referring partner will earn a percentage of the total sale amount that the referred partner generates.

For example, if a partner group has a 20% sale reward + 10% partner referral reward (based on the original sale amount), and the referred partner generates a **\$100 sale**:

* The referred partner will earn a **\$20 commission** (20% sale reward \* \$100 = \$20).
* The referring partner will earn a **\$10 referral** reward (10% partner referral reward \* \$100 = \$10).

<Frame>
  <img alt="Referral reward original sale amount" />
</Frame>

### Referred partner's commission

When you select the **referred partner's commission** option, the referring partner will earn a percentage of the commission that the referred partner earns.

For example, if a partner group has a 20% sale reward + a 10% partner referral reward (based on the referred partner's commission), and the referred partner generates a **\$100 sale**:

* The referred partner will earn a **\$20 commission** (20% sale reward \* \$100 = \$20).
* The referring partner will earn a **\$2 referral** reward (10% partner referral reward \* \$20 commission = \$2).

<Frame>
  <img alt="Referral reward referred partner's commission" />
</Frame>

## Partner view

When your program has a partner referral reward set up, partners will see a **Partner referral rewards** section in the program overview page.

Here they'll find their reward details, and a link to your program's [application form](/help/article/program-application-form) with their referral tracking parameter automatically added.

<Frame>
  <img alt="Partner referrals view" />
</Frame>

## Viewing referred partners

To view the partners referred by a specific partner, you can view these in one of two ways:

<Steps>
  <Step title="Partner profile">
    From a partner's profile page, click the link row from their partner referral link. This will open your **All partners** view filtered to the partners they have referred.

    <Frame>
      <img alt="Partner referrals profile row" />
    </Frame>
  </Step>

  <Step title="All partners page">
    Select the **Referred by** filter from your **All partners** page.

    <Frame>
      <img alt="Partner referrals filter selection" />
    </Frame>

    Then select the referring partner to filter the table to partners they have referred.

    <Frame>
      <img alt="Partner referrals filtered table" />
    </Frame>
  </Step>
</Steps>

## Manually attribute referring partner

If a partner is not attributed to a referring partner, you can assign them manually through the partner's profile page. To assign a referring partner, click **Attribute referring partner** from their profile details section.

<Frame>
  <img alt="Attribute referring button" />
</Frame>

From the **Attribute referring partner** modal, select the partner you want to attribute as the referring partner.

<Frame>
  <img alt="Attribute referring partner modal" />
</Frame>

After selecting the referring partner, you can also select to create commissions for eligible past events. This will generate partner referral commissions for the referring partner based on the referred partner's historical commissions.

<Frame>
  <img alt="Attribute referring partner modal with create commissions option" />
</Frame>

Be sure to confirm your changes, as this action cannot be undone. Click **Attribute partner** to save your changes.


# Configuring partner rewards
Source: https://dub.co/help/article/partner-rewards

Learn how to create custom rewards (pay-per-click, pay-per-lead, pay-per-sale) tailored to each partner with Dub Partners.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

<Frame>
  <video />
</Frame>

On [Dub Partners](/help/article/dub-partners), you can offer different incentives for your partners to incentivize them to create high-quality content to spread the word about your product:

| Reward type                 | Description                                                                                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Click-based (pay-per-click) | Rewarding partners for driving clicks to your product – similar to how Google Ads' pay-per-click works.                                            |
| Lead-based (pay-per-lead)   | A more effective alternative to pay-per-click, where you reward partners for leads generated (e.g. demo requests, free trial signups, etc.)        |
| Sale-based (pay-per-sale)   | This is the most common reward type, where you reward partners for driving sales to your product either via a rev-share or fixed-price commission. |

During the program onboarding flow, you'll be asked to create a [default group reward](#program-wide-rewards) that will be automatically applied to all new partners who join your program.

This initial reward will be either a lead or a sale-based reward by default, but you can customize the reward terms accordingly.

## Default group rewards

After initial onboarding, you can create additional rewards for clicks, leads, or sales, depending on how you initially set up the group. These rewards will be made available to all partners who join your program.

<Frame>
  <img alt="Default group rewards" />
</Frame>

You can create additional rewards for [partner referrals](/help/article/partner-referrals), which allow partners to earn from bringing on new partners to your program.

## Creating a reward

Our reward builder lets you create simple and complex rewards quickly and easily. When the reward builder initially opens, you'll see the basics required to create a reward. Clicking on the different `Blue` and `Orange` input prompts allow you to change the structure of the reward.

<Frame>
  <img alt="Initial create sale reward state" />
</Frame>

### Configuring reward types

Depending on the type of reward you're creating, you can choose between a flat fee or a percentage-based commission:

| Reward Type | Model      | Description                                            | Example           |
| ----------- | ---------- | ------------------------------------------------------ | ----------------- |
| **Click**   | Flat       | Partners receive a fixed fee for each click generated  | \$2 per click     |
| **Lead**    | Flat       | Partners receive a fixed fee for each qualified lead   | \$50 per lead     |
| **Sale**    | Percentage | Partners receive a percentage of sales (revenue share) | 30% of sale value |
| **Sale**    | Flat       | Partners receive a fixed fee for each completed sale   | \$50 per sale     |

For sale rewards, you can further customize the recurring nature of the commission:

* One time
* For a set period (anywhere between 3 months to 3 years)
* For the customer's lifetime

### Adding reward conditions

<Tip>
  Customizing reward conditions is only available on the [Advanced plan and
  above](https://dub.co/pricing/partners).
</Tip>

On Dub, you can also set specific requirements for reward completion and can even reward a different type (flat rate or percentage), amount, and duration when they are met. They are available for sale and lead rewards only, and allow you to set conditions like the following:

* [Geo-specific rewards](#geo-specific) (e.g. higher rewards for sales and leads from a specific country)
* [Product-specific rewards](#product-spotlight) (e.g. higher rewards for sales of specific products)
* [Partner performance tiers](#partner-performance-tiers) (e.g. higher rewards for partners with more than 100 conversions)
* [Staggered reward durations](#staggered-reward-durations) (e.g. higher rewards for the first 12 months, then a lower reward for the rest of the customer's lifetime)
* [Sale amount modifiers](#sale-amount-modifiers) (e.g. higher rewards for sales over a specific amount)
* [Company size tiers](#company-size-tiers) (e.g. higher pay-per-lead rewards for leads from larger companies)
* [Billing cycle rewards](#annual-vs-monthly-billing) (e.g. higher rewards for annual plans vs. monthly plans)

Click **Add condition** to set these parameters.

<Frame>
  <img alt="Initial create sale reward state" />
</Frame>

Each condition can be based on `Customer`, `Sale`, `Partner`, or `Lead`, allowing you to mix and match reward conditions to suit your program best.

Conditions let you use `AND` or `OR` statements to control how they work. Each condition is tied only to its own reward and doesn't affect others. As shown in the example below, your conditions can be as simple or as complex as you need.

<Frame>
  <img alt="Initial create sale reward state" />
</Frame>

### Metadata conditions

In addition to the built-in attributes, you can create conditions based on any custom `metadata` you attach to your tracking events. This lets you reward partners based on data that's specific to your business (e.g. company size, billing cycle, plan tier, or seat count) that Dub doesn't track natively.

Metadata conditions are available for the `Lead` and `Sale` condition types. To create one, choose `Metadata` as the condition field, enter the metadata field name (the key) you want to match against, then pick an operator and value.

Each metadata condition supports two groups of operators, depending on whether you're matching a text or number value:

| Field type | Operators                                                                                    |
| ---------- | -------------------------------------------------------------------------------------------- |
| **Text**   | `is`, `is not`, `starts with`, `ends with`, `contains`, `does not contain`                   |
| **Number** | `is greater than`, `is greater than or equal to`, `is less than`, `is less than or equal to` |

<Frame>
  <img alt="Reward builder metadata condition" />
</Frame>

You can also attach metadata via the `metadata` prop on the [`/track/lead`](/docs/api-reference/track/lead) and [`/track/sale`](/docs/api-reference/track/sale) endpoints. If you're using our [Stripe integration](/docs/integrations/stripe), sale metadata is automatically pulled from your Stripe [`checkout.session` metadata](https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-metadata) for one-time/subscription checkouts and/or your Stripe [`invoice` metadata](https://docs.stripe.com/api/invoices/object#invoice_object-metadata) for recurring invoice payments.

For end-to-end examples, see [Company size tiers](#company-size-tiers) (lead) and [Annual vs. monthly billing](#annual-vs-monthly-billing) (sale).

### Custom reward descriptions

By default, Dub automatically generates reward descriptions when rewards are created. In some cases, you may want to customize how those rewards are presented to partners, whether that’s to better match your brand voice or to make a complex reward easier to understand.
To edit a reward’s content, click the **pencil icon** next to the main reward builder. This opens the content editor, where you can update the primary reward description and optionally add more context using the reward tooltip.

<Frame>
  <img alt="Custom reward description" />
</Frame>

Click `Reward description` to update the main description. You can enter up to 100 characters to clearly explain the reward at a glance.

<Frame>
  <img alt="Editing the reward description" />
</Frame>

Click `Reward tooltip` to edit the tooltip content. Tooltips support basic markdown and allow up to 2,000 characters, making them ideal for adding detailed explanations or terms.

<Frame>
  <img alt="Editing the reward tooltip" />
</Frame>

You can customize each field independently. For example, you might only update the tooltip for a more complex reward while leaving the main description unchanged. You can also do the opposite, updating the description without touching the tooltip. This flexibility lets you tailor each reward without extra work.

<Frame>
  <img alt="Reward preview tooltip hover" />
</Frame>

If you want to remove your custom content, click the **X** icon next to the custom description. The reward will revert to Dub’s default description.

### Reward preview

During the creation of your rewards, the reward preview will populate with the settings you've provided. This preview allows you to see exactly what your partner will see on your [program details](/help/article/program-marketplace#program-details) and within [their dashboard](/help/article/navigating-partner-program#overview).

<Frame>
  <img alt="Reward preview" />
</Frame>

### Reward quality

When you're creating or editing a reward, we'll provide you with a reward quality score based on how attractive and compelling your reward is for potential partners. This score is designed to help you optimize your rewards for maximum partner engagement.

<Frame>
  <img alt="Reward quality score" />
</Frame>

| Quality Score | Reason                                  | Details                                                                                               |
| ------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Low           | **May struggle to attract partners**    | The reward set is below typical ranges, or there is limited earning potential.                        |
| Limited       | **May struggle to attract partners**    | This is a one-time reward, and there's no long-term earning potential.                                |
| Good          | **Solid reward with room to grow**      | The reward meets minimum partner expectations, and there's opportunity to increase earning potential. |
| Strong        | **High earning potential for partners** | The reward level is competitive, and the partner continues to earn after the first sale.              |

Hover over the quality score to see which specific reward factors are contributing to the score.

<Frame>
  <img alt="Reward quality score example" />
</Frame>

### Confirm reward creation

Before the reward is created, you'll be prompted to confirm the details. This is a good opportunity to review the reward and make any final adjustments. Here you can also provide optional notes to your partners that will be added to the reward notification email they'll receive upon creation.

<Frame>
  <img alt="Reward creation confirmation" />
</Frame>

Here's an example of the reward notification email that will be sent to your partners:

<Frame>
  <img alt="Reward notification email" />
</Frame>

## Reward examples

Here are a few reward examples with their conditions to give you some starting ideas.

<Tip>
  Customizing reward conditions is only available on the [Advanced plan and
  above](https://dub.co/pricing/partners).
</Tip>

### Geo-specific

Tailor your reward amount by customer country. In this example, we're rewarding partners with a higher percentage for conversions from Canada and the United States:

<Frame>
  <img alt="Global Boost condition example" />
</Frame>

| Condition                                                   | Reward                                    | Description                                 |
| ----------------------------------------------------------- | ----------------------------------------- | ------------------------------------------- |
| If `Customer` `Country` `is one of` `Canada, United States` | Then pay a `Percentage` of `40%` per sale | Higher reward for US and Canadian customers |
| If `Customer` `Country` `is not` `United States`            | Then pay a `Percentage` of `10%` per sale | Lower reward for non-US customers           |

### Staggered reward durations

Tailor your reward amount by the customer's subscription duration, subscription start date, or signup date.

In this example, we're offering a 25% reward for the first 12 months and a 10% reward for all other sales:

<Frame>
  <img alt="Staggered rewards example" />
</Frame>

There are 3 different conditions for staggered reward durations:

| Condition type                         | Description                                      | Example                                                                  |
| -------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
| **Customer's subscription duration**   | How long the customer has been subscribed for    | 25% for the first 12 months, 10% for the rest of the customer's lifetime |
| **Customer's subscription start date** | The date the customer started their subscription | 25% if subscribed before Jan 1, 2026, else 10%                           |
| **Customer's signup date**             | The date the customer signed up for your product | 25% if signed up before Jan 1, 2026, else 10%                            |

### Partner performance tiers

Tailor your reward amount by partner performance. In this example, we offer a 50% base reward and increase it to 60% based on how many conversions the partner has:

<Frame>
  <img alt="The Climb condition example" />
</Frame>

| Condition                                                               | Reward                                    | Description                                                              |
| ----------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------ |
| If `Partner` `Total conversions` `is greater than or equal to` `200`    | Then pay a `Percentage` of `60%` per sale | Higher reward for partners with more than 200 conversions                |
| If `Partner` `Total revenue` `is greater than or equal to` `$10,000`    | Then pay a `Percentage` of `60%` per sale | Higher reward for partners with more than \$10K in referral revenue      |
| If `Partner` `Total commissions` `is greater than or equal to` `$1,000` | Then pay a `Percentage` of `60%` per sale | Higher reward for partners who have earned more than \$1K in commissions |

<Tip>
  Want to move partners to a different partner group based on their performance
  instead? [Learn more about group move
  rules](/help/article/partner-groups#group-move-rules).
</Tip>

### Product spotlight

Tailor your reward amount by product. In this example, we're rewarding more when the sale is for the Gold Plan:

<Frame>
  <img alt="Product Spotlight condition example" />
</Frame>

| Condition                                  | Reward                                    | Description                           |
| ------------------------------------------ | ----------------------------------------- | ------------------------------------- |
| If `Sale` `Product ID` `is` `prod_xxx`     | Then pay a `Percentage` of `40%` per sale | Higher reward for `prod_xxx` sales    |
| If `Sale` `Product ID` `is not` `prod_xxx` | Then pay a `Percentage` of `10%` per sale | Lower reward for non-`prod_xxx` sales |

<Tip>
  You can also set what the partner sees as the product name by adding a `Shown
      as` field to the reward.

  For example, if you want to show the product as "Gold Plan" instead of the
  actual product ID, you can set the `Shown as` to `Gold Plan`.
</Tip>

<Info>
  Where `Your product ID` is listed, you'll enter your product ID (the
  screenshot uses an example product ID)

  If you're manually tracking a sale event via our `/track/sale` endpoint, you
  can pass along the product ID via the [metadata
  prop](/docs/api-reference/track/sale#body-metadata-one-of-0)
  (e.g. `metadata.productId = "prod_xxx"`).

  If you're using our [Stripe
  integration](/docs/integrations/stripe), the product ID will be the Stripe
  product ID that the subscription is for (starts with `prod_xxx`). If a Stripe invoice contains multiple line
  items (e.g. a base subscription plus an add-on), Dub
  calculate the commission for each line item using the matching reward
  rate. [See full example here](/docs/integrations/stripe#multiple-line-items-per-invoice).
</Info>

### Sale amount modifiers

For creating tiered rewards based on the sale amount. In this example, we're giving our partners a higher reward if the sale amount is over a specific dollar amount:

<Frame>
  <img alt="The Climb condition example" />
</Frame>

| Condition                                               | Reward                                    | Description                        |
| ------------------------------------------------------- | ----------------------------------------- | ---------------------------------- |
| If `Sale` `Amount` `is greater than or equal to` `$100` | Then pay a `Percentage` of `60%` per sale | Higher reward for sales over \$100 |
| If `Sale` `Amount` `is less than or equal to` `$100`    | Then pay a `Percentage` of `60%` per sale | Lower reward for sales under \$100 |

### New vs. recurring sales

Tailor your reward amount based on whether the sale is the customer's first sale or a recurring sale. During reward creation, select `Sale` as the condition type and then choose `Type` as the condition field. This will allow you to differentiate between new and recurring sales.

<Frame>
  <img alt="First-sale bonus condition example" />
</Frame>

In this example, we're rewarding partners with a 80% reward for the first sale while keeping the standard recurring commission for all other sales:

<Frame>
  <img alt="First-sale bonus selected condition example" />
</Frame>

This is especially useful for subscription-based products, where you may want to offer a higher reward for the first sale to incentivize partners to acquire new customers, while offering a lower reward for recurring sales, while combining with other conditions.

| Condition                         | Reward                                    | Description                         |
| --------------------------------- | ----------------------------------------- | ----------------------------------- |
| If `Sale` `Type` `is` `new`       | Then pay a `Percentage` of `80%` per sale | First-sale bonus for new customers  |
| If `Sale` `Type` `is` `recurring` | Then pay a `Percentage` of `30%` per sale | Standard reward for recurring sales |

### Company size tiers

Tailor your pay-per-lead reward based on a [metadata condition](#metadata-conditions). In this example, we're passing a `companySize` field on each lead (via the `metadata` prop on [`/track/lead`](/docs/api-reference/track/lead)) and rewarding a higher amount for enterprise-sized leads:

| Condition                                                   | Reward                  | Description                              |
| ----------------------------------------------------------- | ----------------------- | ---------------------------------------- |
| If `Lead` `companySize` `is greater than or equal to` `500` | Then pay `$75` per lead | Higher reward for enterprise-sized leads |
| If `Lead` `companySize` `is less than` `500`                | Then pay `$15` per lead | Standard reward for smaller leads        |

<Frame>
  <img alt="Reward builder metadata lead example" />
</Frame>

### Annual vs. monthly billing

Tailor your reward amount based on a [metadata condition](#metadata-conditions). In this example, we're passing a `billingCycle` field on each sale (via the `metadata` prop on [`/track/sale`](/docs/api-reference/track/sale)) and rewarding partners more for driving annual plans over monthly plans:

| Condition                               | Reward                                    | Description                       |
| --------------------------------------- | ----------------------------------------- | --------------------------------- |
| If `Sale` `billingCycle` `is` `annual`  | Then pay a `Percentage` of `40%` per sale | Higher reward for annual plans    |
| If `Sale` `billingCycle` `is` `monthly` | Then pay a `Percentage` of `20%` per sale | Standard reward for monthly plans |

<Frame>
  <img alt="Reward builder metadata sale example" />
</Frame>

## Updating a partner group's reward configuration

You can update a [partner group's](/help/article/partner-groups) reward configuration at any time. When this happens, historical commissions won't be updated.

However, the reward configuration for a partner-customer pair will be updated. E.g., if a partner used to be on a one-time reward and was switched to recurring rewards, all subsequent subscription renewals from their **previous referrals** will be eligible for commission again.

To avoid this, instead of editing a given partner group's reward configuration directly, create separate partner groups for one-time vs recurring rewards. This way, when you move partners from one group to the other, the reward configuration for the partner's previous referrals will be preserved.

<Tip>
  When updating a reward, you'll be prompted to [confirm your
  changes](#confirm-reward-creation), and are able to provide optional notes to
  your partners in the update email.
</Tip>

### Group reward update logs

For transparency and auditability, we also keep a history of all reward updates for a given group:

<Frame>
  <img alt="Partner profile page group history." />
</Frame>

This helps you track changes over time and understand the impact of your reward configurations on the [generated partner commissions](/help/article/partner-commissions).


# Partner tags
Source: https://dub.co/help/article/partner-tags

Learn how to organize, filter, and manage partners with tags in your partner program.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Partner tags help you organize partners with flexible labels. You can use tags to track partner types, campaigns, outreach status, lifecycle stage, or any other internal classification that helps your team manage the program.

Unlike [partner groups](/help/article/partner-groups), tags do not control rewards, discounts, or referral link settings. Use groups for program configuration, and use tags for flexible organization and filtering.

Common examples include:

* **Source**: `YouTube`, `Newsletter`, `Agency`, or `Customer referral`
* **Status**: `Needs follow-up`, `Contract sent`, or `High intent`
* **Campaign**: `Q2 launch`, `Black Friday`, or `Webinar cohort`
* **Relationship**: `VIP`, `Strategic`, or `Community`

## Adding tags to partners

This can be done from either the partner table or the partner details view.

### Partner table

With the [tag column](/help/article/managing-program-partners#columns) enabled, click the **tag icon button** to add tags to a partner. This will open the modal view for all your created tags.

<Frame>
  <img alt="Adding partner tags from the partner table" />
</Frame>

You can also bulk edit partner tags using the multi-select checkboxes on each partner row, then click **Update tags** at the top of the table.

<Frame>
  <img alt="Bulk editing partner tags in the partner table" />
</Frame>

### Partner details

In the partner details view, manage tags from the partner's profile side panel by clicking the **Add tag** or the **Manage** button.

<Frame>
  <img alt="Adding partner tags from the partner details view" />
</Frame>

### Tags modal

Both the partner table and partner details view will open the same tags modal, where you can manage all your tags in one place. Any changes you make here will update across the entire program.

From here, you can select any number of existing tags to assign to your partners. Learn more about [managing tags](/help/article/partner-tags#managing-tags).

<Frame>
  <img alt="Selecting partner tags from the tags modal" />
</Frame>

## Managing tags

With the tags modal open, you'll be able to see all the tags that you've created and the number of partners assigned. Here you can create new tags, search, update, and delete existing tags.

### Adding new tags

To add a new tag, click into the text field and type the tag you'd like to create. If one already exists, you'll see the match below. If one doesn't exist, click **Create new tag** to create one.

<Frame>
  <img alt="Creating partner tags from the tags modal" />
</Frame>

This will create the tag and assign it to the partner you're currently editing. You can create as many tags as you'd like and assign multiple tags to a single partner.

<Note>
  Tags have a 190 character limit
</Note>

### Updating tags

To update an existing tag, click the **Edit** option from the tag dropdown. This will open a text field where you can update the name of the tag. This will update the tag name for all partners assigned.

<Frame>
  <img alt="Editing partner tags from the tags modal" />
</Frame>

### Searching tags

Using the text field, search for the tag you're looking for. If there are any matches, you'll see them below. This is especially helpful for programs with a large number of tags.

<Frame>
  <img alt="Searching partner tags from the tags modal" />
</Frame>

### Deleting tags

To delete an existing tag, click the **Delete** option from the tag dropdown. This will remove the tag from all partners assigned, so be sure as this isn't reversible.

<Frame>
  <img alt="Deleting partner tags from the tags modal" />
</Frame>

## Filtering partners by tags

Partner tags can be used alongside other partner filters such as group, status, and location. This makes it easier to segment your partner table for follow-up, reporting, and other workflows.

<Frame>
  <img alt="Filtering partners by tags" />
</Frame>

For example, you can filter for partners tagged `VIP` who are also in a specific [partner group](/help/article/partner-groups), or partners tagged `Needs follow-up` after an outreach campaign.

You can also use the same filtering when viewing your [program analytics](/help/article/program-analytics#filtering-your-data) and [commission analytics](/help/article/commission-analytics#filtering-commission-analytics).

<Frame>
  <img alt="Filtering partners by tags in analytics" />
</Frame>

## Partner tags vs Partner groups

| Feature                         | Tags                             | Groups                                                 |
| ------------------------------- | -------------------------------- | ------------------------------------------------------ |
| Primary use                     | Flexible internal organization   | Program configuration and segmentation                 |
| Relationship to partners        | Many-to-many                     | One-to-many                                            |
| Controls rewards                | No                               | Yes                                                    |
| Controls discounts              | No                               | Yes                                                    |
| Controls referral link settings | No                               | Yes                                                    |
| Best for                        | Labels, workflows, and filtering | Rewards, discounts, applications, and partner movement |


# Frequently Asked Questions
Source: https://dub.co/help/article/partners-faq

Your quick guide to the most common partner questions.

We’ve compiled the most common partner questions in one place, so you can quickly find answers and get back to growing your earnings.

## Account & Settings

### How can I change my country?

For compliance reasons, country changes need to be handled by our team. Please [reach out to support](https://dub.co/contact/support) to change your country, regardless of whether you've already received payouts on Dub.

### How can I change my account type – Business / Individual?

For compliance reasons, you cannot change your account type once you've [received payouts on Dub](/help/article/receiving-payouts). If you still need to change your account type, please [reach out to support](https://dub.co/contact/support).

### How can I update my affiliate link?

You can update your affiliate link under the Links tab of your program.

### How can I update my name, email, or phone number while setting up Stripe Express?

You can set up your account as-is and skip any verification steps. After you have access to your Stripe account, you can update your personal information under the Profile tab. If you are having any issues, please reach out to the Stripe Express team: [https://support.stripe.com/express/](https://support.stripe.com/express/)

## Payouts & Earnings

### Why is my payout still pending?

Your payout is still [pending](/help/article/commissions-payouts#payout-statuses) because the program hasn't paid its affiliates yet. Please reach out directly to your program to learn more about their payout schedule.

Some programs might also require a minimum payout amount before your payouts can be processed (e.g. Framer requires a [\$200 minimum balance](https://www.framer.com/help/articles/getting-started-with-dub/#getting-paid)).

### Why is my commission still pending?

Your commission is pending because there is a [30-day holding period](/help/article/commissions-payouts#what-does-holding-period-mean). After 30 days, your commission will be confirmed and eligible for payout.

### I referred a user but haven't received a commission, what should I do?

If you are certain that you followed the instructions to receive a commission, please wait 48 business hours for the commission to appear. If it is still missing, please reach out to your program and they will investigate.

### My payout is stuck in a "processed" status, what should I do?

If your payout is marked as "processed", it hasn’t been deposited into your connected bank account yet because it is below the `$10` minimum for automatic payouts.

If you want, you can still [withdraw it for a `$0.50` fee](/help/article/receiving-payouts#what-is-the-minimum-withdrawal-amount-and-how-does-it-work). Payouts above \$10 are automatically deposited to your bank account at no additional cost.

### Do you support payouts via Payoneer, PayPal, or crypto?

Unfortunately, those payout methods are not supported at this time. We will let you know as soon as anything changes.

## Stripe Express

### Stripe is invite-only in my country, how do I get an invite?

You don't need an invite. Dub uses [Stripe Express](https://support.stripe.com/express) to process payouts, which is different from the standard Stripe account. If your country is supported by Stripe Express, you will be able to see "Connect bank account" under the [Payouts](https://partners.dub.co/settings/payouts) tab.

Here's the full list of countries that we support sending payouts to:

<PayoutSupportedCountries />

### My country is not supported by Stripe Express, what should I do?

If your country is still not supported by Stripe Express, you can create a legal entity in any of the supported countries above and use that to receive payouts instead. For example, you can create a US LLC (e.g., with [Stripe Atlas](http://stripe.com/atlas)), which would let you receive payouts in USD.

### Can I connect a savings account instead of a checking account?

While it is possible to connect your savings account instead of a checking account, we highly recommend against it for two main reasons:

1. Some banks do not allow third-party deposits into savings accounts, which may cause your payout to fail;
2. Payouts sent to savings accounts can take significantly longer to process (up to 30 business days, depending on the bank), resulting in payout delays.

### My payout is marked as "Paid" on Stripe but it was not deposited, what should I do?

Some banks process payouts longer than usual. Stripe will show the expected arrival date – for example, *"Can’t find your payout? Banks can take up to 5 business days to process payouts. Wait until **\[DATE]** and then contact your bank."*

If you still cannot find your payout after the expected arrival date, please contact your bank and ask to locate the payout (you can find the trace ID in your email). Some banks might require additional information from you to release the funds. If your bank cannot find the payout, please reach out to Dub's support and send us:

* a letter from your bank, confirming that the payout was not received;
* your bank statement.

We will reach out to Stripe on your behalf and investigate this further. Once the payout is marked as "Failed" on Stripe, it will be re-sent again to your bank account. Please update your bank account in advance to ensure you can receive your payouts.

**Common reasons why payouts fail:**

* your local bank account is in USD but your local currency is not USD;
* your bank account number is incorrect;
* your bank account name does not match your name on Stripe.

### Can I connect my company's Stripe account to Dub?

No, you need to create a new account on Stripe Express to receive payouts. Stripe Express and Stripe Standard are different account types.


# How to create password-protected links on Dub?
Source: https://dub.co/help/article/password-protected-links

Learn how to protect your short links with a password on Dub to prevent unauthorized access.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

Dub's password protection feature is useful when you want to share a link with a group of people and you want to make sure that only the intended recipients can access the link.

In this guide, we will learn how to create password-protected links on Dub.

<Steps>
  <Step title="Create a link">
    First, create a link by clicking on the **Create Link** button on the top right corner of the Dub dashboard.

    Then, enter the destination URL and optionally, a custom short link for it.

    <Frame>
      <img alt="Create a link with the Dub link builder" />
    </Frame>
  </Step>

  <Step title="Enter a password">
    To use this feature, click on the **Password** button in the link builder. This will open a new modal **Link Password**.

    Then, enter a password for the link or generate a random one and click on the **Add password** button.

    You can also quickly access the **Link Password** feature by using the keyboard shortcut `P` while in the link builder.

    <Frame>
      <img alt="Entering a password for the link" />
    </Frame>

    Then, when you share your link, the user will be prompted to enter a password before they can access the destination URL.

    <Frame>
      <img alt="An example of a password-protected page" />
    </Frame>
  </Step>

  <Step title="Pass the password in the URL (optional)">
    Optionally, you can also pass a `pw` query parameter to the link to automatically authenticate and redirect the user to the destination URL.

    Here's an example:

    * Password-protected link: [dub.sh/password](https://dub.sh/password)
    * Password-protected link with the password included in the URL as a query parameter: [dub.sh/password?pw=dub](https://dub.sh/password?pw=dub)
  </Step>

  <Step title="Set a custom logo for the password page (optional)">
    On the [Pro plan](https://dub.co/pricing) and above, you can also set a custom logo for the password page. This is useful if you want to brand the password page with your company's logo.

    Your password page logo is connected to your workspace's logo on Dub. You can set that by following the steps below:

    1. Go to your workspace settings page by clicking on the **Settings** link in the menu bar at the top of your workspace dashboard.
    2. Scroll down to the **Workspace Logo** section and upload your company's logo. We recommend using a square image for the best results.

    <Frame>
      <img alt="Workspace Logo section" />
    </Frame>

    Once you've set your workspace's logo, your password page will automatically update to use your workspace's logo.

    <Frame>
      <img alt="Password page with custom logo" />
    </Frame>
  </Step>
</Steps>


# Measuring program performance
Source: https://dub.co/help/article/program-analytics

Track conversion rates, referral sources, UTM parameters, geolocation data and more to understand ROI and performance of your partner program.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

On any given day, Dub tracks upwards of **10 million events across clicks, [conversions](/help/article/dub-conversions), and [commissions](/help/article/partner-commissions).**

With the sheer volume of data that's tracked, it is important to be able to easily filter and create reports to help marketing teams make informed decisions.

In this guide, we'll walk you through how to use the program analytics tab get the most out of your partner program's events and engagement data.

## Where to find your program analytics?

When viewing your Partner Program navigation, click on the **Analytics** page, in the **Insights** group:

<Frame>
  <img alt="Program analytics location" />
</Frame>

## Program analytics views

Your program analytics dashboard consists of several views:

1. [Analytics chart](#1-analytics-chart)
2. [Partner data table](#2-partner-data-table)
3. [Aggregated data for different facets (top views)](#3-aggregated-data-for-different-facets-top-views)

### 1. Analytics chart

This is the default view and shows you the number of click events in a time-series chart over time. You can also select leads or sales to view their chart and data.

**Performance and Commissions data selector**

At the top of the analytics chart, you can switch between **Performance** and **Commissions**. Use **Performance** to analyze clicks, leads, sales, and conversion trends. Use **Commissions** to focus on commission earnings over time.

For a deeper look at commission-specific reporting, see [Commission analytics](/help/article/commission-analytics).

<Frame>
  <img alt="Time-series chart" />
</Frame>

**Data toggle**

You can change the data displayed by clicking the `$ | 123` toggle in the top right of the data type section.

By default `$` is selected, as it related directly to your [reward types](/help/article/partner-rewards). Switching to `123` will change any monetary values to their specific reward count. If your program has other monetary rewards (clicks, leads, sales), their dollar amount will show as well.

<Frame>
  <img alt="Time-series chart" />
</Frame>

**Chart toggle**

You can change the chart type displayed by clicking the `time-chart | conversion funnel` toggle in the top right of the chart area.

<Frame>
  <img alt="Time-series chart" />
</Frame>

When the chart type is switched to **conversion funnel**, you can view the percentage of customers that complete each stage of the sale conversion. In this example, 0.58% of clicks have resulted in a customer making a purchase.

<Frame>
  <img alt="Time-series chart" />
</Frame>

### 2. Partner data table

This table shows your partners performance for each of the data types from the time-series chart (clicks, leads, and sales). The matching data column is sorted by most-to-least, so you can quickly see who is the top performer for that category.

<Frame>
  <img alt="Partner data table" />
</Frame>

### 3. Aggregated data for different facets (top views)

These are more commonly known as the "Top Views" in Dub Analytics. These views show your top links, countries, cities, devices, and more.

<Frame>
  <img alt="Aggregated data for different facets" />
</Frame>

Each section has their own tabs to select different data for you to explore your program data further. These are grouped by the following categories:

* **Links** - Short Links and Destination URLs
* **Referrals** - Referrers and UTM Parameters
* **Location** - Countries, Cities, Regions, and Continents
* **Tech** - Devices, Browser, OS, and Triggers

## Filtering your data

With all this data, it's important to be able to filter and make better decisions. You can click the **Filter** button in the top left will show you the options available.

<Frame>
  <img alt="Filtering your program analytics" />
</Frame>

| Filter    | Description                                                                                          |
| --------- | ---------------------------------------------------------------------------------------------------- |
| Ask AI    | Use natural language to query your analytics - eg: "mobile chrome users US only", "UK android users" |
| Group     | Select a specific [partner group](/help/article/partner-groups)                                      |
| Tag       | Select a specific [partner tag](/help/article/partner-tags)                                          |
| Partner   | Select a specific program partner                                                                    |
| Country   | Select a specific county                                                                             |
| City      | Select a specific city                                                                               |
| Continent | Select a specific continent - N. America, Europe, etc                                                |
| Device    | Select a specific device - Desktop, Mobile, Tablet, etc                                              |
| Browser   | Select a specific browser - Chrome, Safari, Firefox, etc                                             |
| OS        | Select a specific operating system - Windows, Mac OS, etc                                            |
| Referer   | Select the specific website a visitor arrived from                                                   |


# Customizing your program application form
Source: https://dub.co/help/article/program-application-form

Learn how to customize your program application form to collect the right information you need and select the best partners to work with for your program.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

## What is the program application form?

The program application form collects the information you want from people applying to [your program](/help/article/dub-partners). Use it to ask about websites, social accounts, experience, audience size, or any details that help you decide who to accept.

<ImageLink alt="Branded program application form" href="https://partners.dub.co/dub/apply" />

<Tip>
  On Dub, you can create a custom application form for each [partner
  group](/help/article/partner-groups) in your program. This is helpful for
  collecting different information from different partner types – e.g.
  influencers vs affiliates vs resellers.

  By default, when you create a new partner group, it reuses the application
  form details from your default partner group, but you can always edit them
  afterwards.
</Tip>

## Where to find the application form builder?

From your **Groups** page, open the [partner group](/help/article/partner-groups) you want to tailor the application form for.

<Frame>
  <img alt="Group selection" />
</Frame>

From the tab navigation, click **Branding,** then in the content panel, choose **Application form** to open the editor.

<Frame>
  <img alt="Branding page within your dashboard" />
</Frame>

For your convenience, [here's a quick link](https://app.dub.co/program/groups/default/branding?tab=application) that brings you directly to the application form builder for your program's default group.

## Mandatory fields

Name, email, and country are always required and appear at the top of every form. You cannot remove or rename them.

<Frame>
  <img alt="Mandatory fields" />
</Frame>

## Editing and adding fields

Hover over form content in the builder to reveal actions for adding, editing, deleting, and reordering fields. Content that appears above the form can only be edited. This keeps the page layout consistent for applicants.

<Frame>
  <img alt="Brand page within your dashboard" />
</Frame>

## Input field types

Click **Insert field** to open the selector, then pick the input type you want to add.

<Frame>
  <img alt="Insert field" />
</Frame>

### Short text

Use for short answers like names, one-line bios, or URLs.

<Frame>
  <img alt="Adding short text fields" />
</Frame>

| Option            | Details                               | Example                             |
| ----------------- | ------------------------------------- | ----------------------------------- |
| Input label       | The text above the field              | *"Link to your top social profile"* |
| Input placeholder | Guidance shown inside the empty field | *"x.com/steventey"*                 |
| Required          | Make the field mandatory              | Yes                                 |
| Max characters    | Set a character limit when needed     | 120                                 |

### Long text

Use for longer responses that need multiple lines.

<Frame>
  <img alt="Adding long text fields" />
</Frame>

| Option            | Details                               | Example                                                     |
| ----------------- | ------------------------------------- | ----------------------------------------------------------- |
| Input label       | The text above the field              | "Tell us about your content strategy"                       |
| Input placeholder | Guidance shown inside the empty field | "Describe your approach to creating and sharing content..." |
| Required          | Make the field mandatory              | Yes                                                         |
| Max characters    | Set a character limit when needed     | 500                                                         |

### Dropdown

Best when you have more than five options. You can add, reorder, and delete options at any time.

<Frame>
  <img alt="Adding files" />
</Frame>

| Option                    | Details                                   | Example                                                     |
| ------------------------- | ----------------------------------------- | ----------------------------------------------------------- |
| Input label               | The text above the field                  | "Which platforms do you use?"                               |
| Required                  | Make this field mandatory                 | No                                                          |
| Allow multiple selections | Toggle this to allow more than one choice | Yes (for checkboxes)                                        |
| Options                   | The choices the applicant sees            | "Instagram", "TikTok", "YouTube", "LinkedIn", "X (Twitter)" |

### Multiple choice

The multiple-choice input can serve multiple purposes:

1. **Radio** - A list of items that an applicant can select only one from
2. **Checkbox** - A list of items that an applicant can select one or many from

By default, the initial input is set to the radio option, but the input can adapt to your needs.

<Frame>
  <img alt="Adding accordion content" />
</Frame>

| Option      | Details                        | Example                              |
| ----------- | ------------------------------ | ------------------------------------ |
| Input label | The text above the field       | "What's your primary content focus?" |
| Required    | Make the field mandatory       | Yes                                  |
| Options     | The choices the applicant sees | "Tech & SaaS", "Business & Finance"  |

### Image upload

Add an image field to your application so partners can share proof, examples, or supporting materials.

<Frame>
  <img alt="Adding image upload" />
</Frame>

| Option                | Details                                                               | Example                 |
| --------------------- | --------------------------------------------------------------------- | ----------------------- |
| Input label           | The text above the field                                              | "Analytics screenshots" |
| Allow multiple images | Enable partners to upload multiple images. Minimum 2, and maximum 10. |                         |
| Required              | Make the field mandatory                                              |                         |

### Website and socials

Ask for specific website or social accounts. Toggle each network on or off and mark any required. For example, you might require Instagram but make X optional.

<Frame>
  <img alt="Adding website and social inputs" />
</Frame>

<Tip>
  You can only add one website and socials section per form. If one is already
  present the input type will not appear in the Insert field menu.
</Tip>

## Publishing your changes

When you are ready, click Publish in the top right to make your changes live.

<Frame>
  <img alt="Publish button" />
</Frame>

##


# Reviewing program applications
Source: https://dub.co/help/article/program-applications

Learn how to manage your pending applications, and to bring the best partners to your program.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

## Where to find your program applications

In the **Partner Program** menu, click **Applications** to view your pending applications. We also show you a count badge, so you can see how many pending applicants you have at all times.

<Frame>
  <img alt="Program applications location" />
</Frame>

## Applicant table

On your applications table, you'll be able to view key information, including:

* The applicant's name
* When they applied
* Their provided location
* Their website and social media profiles

<Frame>
  <img alt="Applications table" />
</Frame>

Click on any of the rows to view their profile and application form.

## Application settings

Click the **Application settings** button in the top right corner to configure your application and [marketplace settings](#marketplace-settings).

<Frame>
  <img alt="Application settings button" />
</Frame>

### Eligibility requirements

Adding eligibility criteria allows you to define the conditions that must be satisfied for an applicant to apply. Click **Add condition** to start defining your criteria.

<Frame>
  <img alt="Application settings criteria" />
</Frame>

In the example above, the applicant must be from Canada, United States, or Mexico to apply. If they're from any country other than these, they won't be able to apply.

| Logic     | Description                    |
| --------- | ------------------------------ |
| Condition | Select from `is` or `is not`   |
| Country   | Select any number of countries |

### Marketplace settings

If your program is listed in the [Dub program marketplace](https://dub.co/help/article/program-marketplace), click the **Marketplace** toggle to access the marketplace settings. Here you can set the description that's shown to partners, and categories to help find your program more easily.

<Frame>
  <img alt="Marketplace settings" />
</Frame>

For product categories, you can select from the following options:

* Finance
* Marketing
* AI
* Development
* Design
* Productivity
* Ecommerce
* Security
* Education
* Health
* Consumer

## Partner application

When the application is open, we show additional information collected during the application process and their account creation. This is a good opportunity to get to know more about this partner and their background if they've provided it.

<Frame>
  <img alt="Application details" />
</Frame>

### Online presence

During account creation, we allow the partners to provide their website and any social media accounts. As part of this, we also allow partners to verify each of these items to increase trust with their application.

Verification isn't required, but encouraged as it reduces the time it takes to review applications.

### Verified badge

If a partner has verified their website or social media accounts, a green check mark badge will be shown beside the item. In some scenarios, it will also show statistics from that verified account.

<Frame>
  <img alt="Verified account badge" />
</Frame>

### Additional information

The partner card will provide additional information about the partner, helping you understand how their profile is set up, and optional details they've provided.

<Frame>
  <img alt="Additional information" />
</Frame>

| Item                         | Description                                                                                                                         |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Location                     | Declared country of residence                                                                                                       |
| Company name (optional)      | Partner's company name, if they've chosen to have a company profile                                                                 |
| Application date             | Date of application submission                                                                                                      |
| Payout method connected      | Shown when the partner has connected a [payout method](/help/article/receiving-payouts), and what that type is                      |
| Verified identity (optional) | Whether the partner has verified their identity by submitting a government-issued ID (Powered by [Veriff](https://www.veriff.com/)) |

### Application history

If a partner previously applied and was [rejected](#rejecting-applicants), you can review their application history by clicking on the **Application** dropdown to select previous versions. This is helpful to see if the application has changed since the last time it was reviewed, and if feedback provided has been addressed.

When viewing historical applications, you can see the reason for rejection and additional notes if they were provided by your team.

<Frame>
  <img alt="Previous applications" />
</Frame>

## Applicant risk flags

<Warning>
  We recommended that you review partners flagged for fraud and the information
  provided before making a final decision.
</Warning>

Occasionally, certain applications to your program might trigger a risk flag that you should review before approving them.

These signals are powered by our [Risk Monitoring](/help/article/risk-monitoring) feature and are meant to protect your partner program from risk and potential fraud.

<Frame>
  <img alt="Application risk flag" />
</Frame>

Here are the reasons an application will be flagged for review:

### High risk

Indicates strong signs of potential fraud or past issues. These applicants need careful review before you move forward.

| Flag                       | Description                                                                                                                                               |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fraud report               | This partner was reported for suspected fraud by another program.                                                                                         |
| Network-level ban          | This partner was banned from another program on Dub. Our team reviewed this decision and confirmed the fraudulent behavior or terms of service violation. |
| Duplicate account detected | This partner was flagged by our system for having 2 or more Dub accounts. Please review to prevent abuse of program restrictions, caps, or bonuses.       |
| Duplicate partner account  | Multiple partner accounts originate from the same device, suggesting duplicate account creation or fraudulent behavior.                                   |

### Medium risk

Generally signals missing or incomplete information. These applicants need a closer look before approving.

| Flag                             | Description                                                                                                   |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| No website or social links added | This partner hasn't provided any social or web presence, making verification harder.                          |
| Geolocation mismatch             | This partner's physical location doesn't match their profile country, which may indicate a location mismatch. |

### Low risk

These tend to be generally low risk and more common, but worth reviewing.

| Flag                                | Description                                                                               |
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
| Email domain mismatch with website  | The custom email domain doesn't match the website provided.                               |
| Masked email address                | Uses a masked email address. Not harmful but harder to verify or contact directly.        |
| No verified website or social links | Partner hasn't verified their website or any social presence, making verification harder. |

<Info>
  Learn more about how [Risk Monitoring](/help/article/risk-monitoring) works on
  Dub.
</Info>

## Approving applicants

There are 3 ways to approve pending applicants to your program:

### Via the application form

When the application form is open, click **Approve** at the bottom of the screen.

<Frame>
  <img alt="Approve button" />
</Frame>

Once you're finished reviewing the application, click **Approve,** and the applicant is now part of your program.

### Bulk approve

If you have multiple applications, use the checkboxes to the left of each applicant to select any that are ready to be approved. You can also select all, by clicking the checkbox in the top left of the application table.

<Frame>
  <img alt="Select all" />
</Frame>

Once selected, click **Approve** to add them to your program instantly.

### Auto-approve

To auto-approve any new pending applications for your program, go to your [individual groups](/help/article/partner-groups) and click on the **Settings** tab. Here you'll find the auto-approve toggle in the "Additional settings" group.

<Frame>
  <img alt="Group settings" />
</Frame>

When you update the auto-approve options, you can apply the new setting to every group at once. Just select the box labelled `Apply to all groups` before confirming. The change will take effect right away.

<Frame>
  <img alt="Group settings" />
</Frame>

With **auto-approve enabled**, any new pending applications will be automatically approved for your program with no review period. Any existing pending applications will still need manual approval before being accepted.

<Warning>
  Applications with a [risk flag](#applicant-risk-flags) are never auto-approved
  and will always require manual review.
</Warning>

To disable auto-approve, click the toggle and confirm your choice. When confirming the change, you're able to apply to all groups as well.

## Rejecting applicants

Similar to the approval process, you can reject an application from the [applicant form](/help/article/program-applications#applicant-form) or use [bulk selection](/help/article/program-applications#bulk-approve) for more than one applicant at a time.

<Frame>
  <img alt="Reject application" />
</Frame>

#### Optional fields

When rejecting an applicant, optionally you can add:

* **Reason for rejection** - Select from a list of common reasons for the rejection.
* **Add additional notes** - Provide more context to the applicant about why they were rejected, and what they can do to improve their chances in the future.

Both fields are shared with the applicant if provided.

#### Reapplication timeframe

By default, rejected applicants cannot reapply to your program for **30 days**, but you can allow them to reapply immediately if you think they have potential and want to give them another chance, or set to **Never** to block them from reapplying at again.

#### Flagging for potential fraud

If you have concerns about the legitimacy of an applicant, you can flag them for potential fraud. This will trigger a review by our team and may result in a ban from all programs on Dub if fraudulent behavior is confirmed. Please provide as much information as possible in the notes when flagging for fraud to help our team with the review.

### Approving a previously rejected partner

If you rejected a partner by mistake, or have changed your decision, you can approve them at any time. Open the **`⋮`** menu in the Applications view and select **View rejected partners**.

<Frame>
  <img alt="Application dropdown" />
</Frame>

From the rejected partners table, find the application you’d like to revisit and click the row to open it.

<Frame>
  <img alt="Application dropdown" />
</Frame>

Once inside the application, click **Approve**. The partner will immediately be enrolled in your program.

<Frame>
  <img alt="Application dropdown" />
</Frame>

##


# Program bounties
Source: https://dub.co/help/article/program-bounties

Drive partner engagement by creating performance and submission bounties for your partner program

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

<Frame>
  <img alt="Program bounties" />
</Frame>

Bounties on [Dub Partners](https://dub.co/partners) let you reward partners for achieving goals or creating content about your product. This guide walks you through creating bounties, setting requirements, and managing submissions.

To view and manage your bounties, go to your **Partner Program** menu and click on **Bounties** under the *Engagement* section:

<Frame>
  <img alt="Bounty navigation location" />
</Frame>

## Creating a bounty

Click **Create bounty** (or use the `C` keyboard shortcut) to open the bounty creation panel. From here, select the type of bounty you want to create:

* [Performance bounties](#performance-bounties)
* [Submission bounties](#submission-bounties)
* [Social metrics bounties](#social-metrics)

<Info>
  Whenever you create a new bounty, an email notification will be sent to all
  eligible partners at the scheduled start date of the bounty. Make sure to
  finalize your bounty before creating it.

  <Frame>
    <img alt="Bounty email notification" />
  </Frame>
</Info>

## Performance bounties

Performance bounties reward partners for achieving measurable milestones, such as a specific number of leads, conversions, revenue, or commissions. They’re tracked automatically and paid out once completed, with no approval needed.

<Frame>
  <img alt="Create performance bounty" />
</Frame>

Examples:

* Drive 500 leads and earn an additional \$1,000 bonus
* Generate \$1,000 in revenue and earn an additional \$250 bonus
* Earn \$5,000 in commissions and receive an additional \$500 bonus

| Input                  | Description                                                                                                                                                                                                                      |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Description (optional) | Provide additional bounty details. E.g. "This is a one-time promotional period for you to earn additional commissions on top of what you're already earning with our program"                                                    |
| Start date (optional)  | Bounty start date. Partners will receive an email notification on this date about the bounty. If one is not selected, the bounty will start on the creation date. [Learn more](/help/article/program-bounties#bounty-start-date) |
| End date (optional)    | Bounty end date. If set, bounty will "expire" after the end date and will no longer be eligible for commission. [Learn more](/help/article/program-bounties#bounty-end-date)                                                     |
| Criteria               | The success criteria are based on: total leads, total conversions, total revenue, or total commissions. [Learn more](/help/article/program-bounties#performance-logic).                                                          |
| Reward                 | The reward amount the partner will receive upon completing the bounty.                                                                                                                                                           |
| Groups                 | [Partner group](/help/article/partner-groups) eligibility. The same bounty can be applied to multiple [partner groups](/help/article/partner-groups) – or all groups in your program.                                            |

### Performance logic

When setting the logic for a performance bounty, you can choose from several options depending on how you want performance to be measured.

<Frame>
  <img alt="Performance bounty logic" />
</Frame>

**Scope** determines how performance is tracked over a specific period.

* **New** - Tracks only performance generated after the bounty starts. For example, if you set a bounty that rewards partners after 10 leads, each partner must generate 10 new leads after the bounty goes live, regardless of how many leads they had before. For revenue-based bounties, only revenue from customers whose first sale happens after the bounty starts is counted.
* **Lifetime** - Counts performance across the partner’s entire lifetime. For example, if you set a bounty that rewards after 10 leads, any partner who already has at least 10 leads will immediately qualify for the reward.

**Activity** determines which metric is tracked for the bounty. Revenue and commissions require a dollar amount to be set.

* Leads
* Conversions
* Revenue
* Commissions

## Submission bounties

Submission bounties reward partners for completing activities that require review, such as creating social media content, publishing a blog post, or hosting an event. Partners submit proof, and rewards are approved once requirements are met.

<Frame>
  <img alt="Create submission bounty" />
</Frame>

Examples:

* Release a YouTube video to drive awareness
* Publish a blog post highlighting your product
* Host an event (in-person or online) promoting your brand

| Input                                                    | Description                                                                                                                                                                                                                      |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name                                                     | The name that will be shown to your partners and internally.                                                                                                                                                                     |
| Description (optional)                                   | Provide additional bounty details. E.g. "Make sure to attach up to 3 photos of your event as proof for bounty completion."                                                                                                       |
| Start date (optional)                                    | Bounty start date. Partners will receive an email notification on this date about the bounty. If one is not selected, the bounty will start on the creation date. [Learn more](/help/article/program-bounties#bounty-start-date) |
| End date (optional)                                      | Bounty end date. If set, bounty will "expire" after the end date and will no longer be eligible for commission. [Learn more](/help/article/program-bounties#bounty-end-date)                                                     |
| Allowed submissions                                      | The number of submissions required for a partner to complete the bounty. Partners must submit this many entries to complete it.                                                                                                  |
| [Submission window](#submission-window) (optional)       | Set how many days before the end date partners can start submitting entries. An end date is required to enter a submission window.                                                                                               |
| [Submission frequency](#submission-frequency) (optional) | Set the time between submissions partners can make. [Learn more](/help/article/program-bounties#submission-frequency)                                                                                                            |
| Criteria                                                 | Set the partner submission requirements.                                                                                                                                                                                         |
| Reward                                                   | The reward amount the partner will receive upon completing the bounty.                                                                                                                                                           |
| Groups                                                   | Partner group eligibility. The same bounty can be applied to multiple [partner groups](/help/article/partner-groups) – or all groups in your program.                                                                            |

### Submission window

This is the number of days before the end date that partners can start submitting entries. **An end date is required to enter a submission window.**

<Frame>
  <img alt="Submission window" />
</Frame>

### Submission frequency

Set the time between submissions partners can make when `Allowed submissions` is greater than one. Partners must submit the full number of allowed submissions to complete the bounty. You can select from:

* Once a day
* Once a week
* Once a month

**Set `Allowed submissions` to greater than one to set a submission frequency.**

<Frame>
  <img alt="Submission frequency" />
</Frame>

If no submission frequency is set, partners can submit all required entries any time the bounty is open for submissions.

### Submission criteria

Add additional requirements to be able to submit a bounty. This includes:

* **Requiring at least one image**: Helpful when a partner needs to provide proof of completion. Up to four images can be included.
* **Requiring at least one URL**: Helpful when a partner needs to provide a URL as proof of completion. Up to 100 URLs can be provided, and the [allowed domains](#allowed-domains) can be configured.

<Frame>
  <img alt="Submission requirements" />
</Frame>

By default, partners claiming a submission bounty are asked to describe how they completed the bounty using a text field.

<Frame>
  <img alt="Submission default" />
</Frame>

#### Allowed domains

You can restrict submissions to specific domains by defining an allowed domain list.

<Frame>
  <img alt="Allowed domains" />
</Frame>

This is helpful when submissions must come from specific platforms or websites,
such as approved social networks or owned domains.

<Tip>
  Looking to run bounties for social posts with outcome-based rewards? Check out
  the [social metrics submission type](#social-metrics) instead.
</Tip>

#### Custom reward

For certain submission bounties, you may want to have a custom reward for each submission. Here are some examples:

* Pay a custom reward amount for an arbitrary task (e.g. organizing a meetup/webinar)
* Reward 10% if the partner reaches a goal they set by a specific date (e.g. [#FramerChallenge](https://www.framer.com/challenge/))

<Frame>
  <img alt="Create submission bounty" />
</Frame>

## Social metrics

<Tip>
  Social metrics bounties are only available on [Advanced plan and
  above](https://dub.co/pricing/partners).
</Tip>

Social metrics bounties are a type of submission bounty that is best used to reward partners based on the performance of their social posts.

<Frame>
  <img alt="Create submission bounty" />
</Frame>

When submitting these bounties, partners can only provide one link, and it's required to be from a verified social account on their [partner profile](/help/article/partner-profile#website-and-socials).

The post they submit must also be created after the bounty has started. This is to avoid partners submitting older content that already has activity.

<Frame>
  <img alt="Setting social metric submissions criteria" />
</Frame>

<Tip>
  Similar to submission bounties, you can also set a [submission
  window](#submission-window) and [submission frequency](#submission-frequency)
  for social metrics bounties.
</Tip>

### Select a social platform and target metric

You can track performance on one social platform per bounty, and can select from:

| YouTube |                   |
| ------- | ----------------- |
| Likes   | Total video likes |
| Views   | Total video views |

| Instagram |                          |
| --------- | ------------------------ |
| Likes     | Total post or reel likes |
| Views     | Total post or reel views |

| TikTok |                  |
| ------ | ---------------- |
| Likes  | Total post likes |
| Views  | Total post views |

| X/Twitter |                  |
| --------- | ---------------- |
| Likes     | Total post likes |
| Views     | Total post views |

| LinkedIn  |                      |
| --------- | -------------------- |
| Reactions | Total post reactions |

### Add a variable bonus

You can also optionally reward for additional performance beyond the base criteria.

<Frame>
  <img alt="Social metric — variable bonus configuration" />
</Frame>

In this example, the program is rewarding an additional \$10 per 100 likes on the submitted post, up to 1,000 likes.

## Bounty Duration

For all bounty types, you can set a start and end date for the bounty. By default, all bounties start on the creation date, and have no end date.

<Frame>
  <img alt="Bounty duration" />
</Frame>

### Bounty start date

For the bounty start date, you can select from the following options:

| Start date               |                                                                                                                                                     |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Today                    | The default selection. Starts when the bounty is created.                                                                                           |
| In 2 weeks               | Starts 2 weeks after the bounty is created.                                                                                                         |
| In 1 month               | Starts 1 month after the bounty is created.                                                                                                         |
| In 6 months              | Starts 6 months after the bounty is created.                                                                                                        |
| When a new partner joins | Starts when a new partner joins your program. The bounty will not be visible to existing partners that have joined before the bounty creation date. |
| Custom                   | Select a specific date for the bounty to start using a date picker.                                                                                 |

<Frame>
  <img alt="Bounty start date" />
</Frame>

### Bounty end date

For the bounty end date, you can select from the following options:

| End date |                                                                   |
| -------- | ----------------------------------------------------------------- |
| Never    | The default selection. The bounty eligibility never ends.         |
| 2 weeks  | Ends 2 weeks after the bounty start date.                         |
| 1 month  | Ends 1 month after the bounty start date.                         |
| 6 months | Ends 6 months after the bounty start date.                        |
| Custom   | Select a specific date for the bounty to end using a date picker. |

<Frame>
  <img alt="Bounty end date" />
</Frame>

## Reviewing bounties

For submission bounties, you must review partner submission entries before [approving](#approving-submissions) or [rejecting](#rejecting-submissions) them. To view a submission, click its row in the submissions table.

<Frame>
  <img alt="Bounty review table" />
</Frame>

In the submission review, you'll see something similar to the examples below, but your view may differ based on the requirements and criteria you've set for the bounty. Each review includes partner information, submission details, and the proof or social content provided to fulfill the criteria.

<Frame>
  <img alt="Bounty review examples" />
</Frame>

From this view, you can either [approve](#approving-submissions) or [reject](#rejecting-submissions) the submission.

### Approving submissions

Once you approve a bounty submission, a [commission](/help/article/partner-commissions) will be automatically created and due at your next payout. The partner will also receive a notification email:

<Note>
  Bounty commissions are recorded as `custom` commissions, which are **not**
  subject to your program's [payout holding
  period](/help/article/partner-payouts#payout-holding-period). They become
  eligible for payout immediately at the next payout cycle – whether the
  submission is approved via the dashboard or the
  [API](/docs/api-reference/endpoint/approve-a-bounty-submission).
</Note>

<Frame>
  <img alt="Bounty approved email" />
</Frame>

<Info>
  If you set your reward to **custom** during the approval process, you'll be
  asked to enter the flat rate amount to reward this partner.
</Info>

### Rejecting submissions

When rejecting a bounty submission, you can also select a reason and provide more details for the rejection. This information will be added to the submission record, and will also be sent in an email to the partner.

<Frame>
  <img alt="Bounty rejected modal" />
</Frame>

For the rejection reason, you can select from:

* Invalid proof
* Duplicate submission
* Out of time window
* Did not meet criteria
* Other

When rejected, no commission will be created, and the partner email will look similar to the example below.

<Frame>
  <img alt="Bounty rejected email" />
</Frame>


# Creating a landing page for your partner program
Source: https://dub.co/help/article/program-landing-page

Create an effective and compelling landing page to attract high-quality partners to join your program and promote your product.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

On Dub, you can also create a branded landing page with details about your [partner program](/help/article/dub-partners):

<Frame>
  <video />
</Frame>

## What is the program landing page?

The program landing page gives your applicants information about your program and the rewards offered. This also includes brand or marketing files, FAQ sections, and any other content you want to add that will help an applicant understand your program, what your product is, and why they should apply.

<ImageLink alt="Branded program application form" href="https://partners.dub.co/dub" />

<Tip>
  On Dub, you can create a custom landing page for each [partner
  group](/help/article/partner-groups) in your program. This is helpful for
  showcasing different information about your program tailored to different
  partner types – e.g. influencers vs affiliates vs resellers.

  By default, when you create a new partner group, it reuses the landing page
  details from your default partner group, but you can always edit them
  afterwards.
</Tip>

## Where to find the landing page builder?

From your **Groups** page, open the group you want to customize the landing page for:

<Frame>
  <img alt="Group selection" />
</Frame>

From the tab navigation, click **Branding,** then in the content panel, choose **Landing page** to open the editor.

<Frame>
  <img alt="Branding page within your dashboard" />
</Frame>

## Editing and adding content

As you hover over the landing page content in the builder, you'll notice that actions will show, allowing you to add, edit, delete, and reorder content blocks within the page.

<Frame>
  <img alt="Brand page within your dashboard" />
</Frame>

Content above the reward section can only be edited. This is to maintain content
consistency. Content below the **"Apply today"** button can be added, edited,
removed, and reordered. ## Content types Clicking on "Insert block" allows you
to select the content type you want to add.

<Frame>
  <img alt="Brand page within your dashboard" />
</Frame>

### Text

Best used for text and paragraph content. This section also uses markdown formatting when editing.

<Frame>
  <img alt="Adding text content" />
</Frame>

### Image

For adding product screenshots and other complementary visuals to the program.

<Frame>
  <img alt="Adding image content" />
</Frame>

Images follow your landing page’s content width by default, keeping visuals aligned without extra layout controls.

<Frame>
  <img alt="Lander image guide" />
</Frame>

### Files

For adding product screenshots and other complementary visuals to the program.

<Frame>
  <img alt="Adding files" />
</Frame>

### Accordion

Best used for FAQ sections and other collapsible related content.
This section also uses markdown formatting when editing.

<Frame>
  <img alt="Adding accordion content" />
</Frame>

### Earnings calculator

Give your partners a sense of how much they can earn, based on your default rewards.

<Frame>
  <img alt="Adding earnings calculator" />
</Frame>

## Generating your landing page with AI

You can also use our AI landing page generator to quickly scaffold a landing page for your affiliate program.

Our AI will scrape your website, retrieve all relevant information, and craft a compelling landing page with your company's details, pricing information, features, and more.

To get started, simply click the "Generate" button and enter your website URL (it should already be prefilled from your program settings):

<Frame>
  <img alt="Generate lander AI button" />
</Frame>

Here's a quick demo of how it looks:

<Frame>
  <video />
</Frame>

<Warning>
  AI can make mistakes – make sure to review all generated content for accuracy
  and style before publishing your changes.
</Warning>

## Publishing your changes

Once you're finished making your content changes, click the Publish button in the top right corner to make sure the changes go live.

<Frame>
  <img alt="Publish button" />
</Frame>

## Landing page examples

Get inspired and check out some of these great examples that companies have already created:

<ImageCarousel />

## Markdown styles available

| Content         | Example                                              |
| --------------- | ---------------------------------------------------- |
| Headings        | `# H1` , `# H2` , `# H3`                             |
| Bold            | `**bold text**`                                      |
| Italic          | `*italicized text*`                                  |
| Blockquote      | `> blockquote`                                       |
| Horizontal rule | `---`                                                |
| Link            | `[title](https://www.example.com)`                   |
| Ordered list    | `1. First item` , `2. Second item` , `3. Third item` |
| Unordered list  | `- First item` , `- Second item` , `- Third item`    |


# Applying to programs via the Dub Program Marketplace
Source: https://dub.co/help/article/program-marketplace

Explore the Dub program marketplace to discover new programs, learn what rewards they offer, and apply to the ones that fit your content and audience.

If you want to expand your partnerships or join new programs on Dub, this guide walks you through how to browse available programs, review their details, and submit an application.

## Where to find the Program Marketplace

From **Your Programs** navigation, click **Marketplace**.

<Frame>
  <img alt="Program Marketplace in the navigation" />
</Frame>

Here you can browse featured programs, marketplace categories, and programs in each category to learn more about and [apply to](/help/article/program-marketplace#applying-to-a-program).

## All programs

To view all programs in the marketplace, click **View all programs** or select a category from the list. This view will show you all the Dub programs available to apply to, along with their reward types and categories.

<Frame>
  <img alt="All available programs" />
</Frame>

On the program cards, you'll get a quick overview of the program, including the rewards offered, the program categories, a brief description of the company, and your [enrolment status](/help/article/program-marketplace#enrolment-status). Hover over some of the details to see more information.

<Frame>
  <img alt="Program card details" />
</Frame>

Click the program card to view the full details of the [program](/help/article/program-marketplace#viewing-program-details).

### Filtering

You can filter the programs available by their reward type, category, and [enrolment status](/help/article/program-marketplace#enrolment-status).

<Frame>
  <img alt="Marketplace filtering" />
</Frame>

### Sorting

You can also sort the results by most popular, newest, or alphabetical order. By default the grid is sorted by most popular, giving a sense of what programs to apply to first.

<Frame>
  <img alt="Marketplace sorting" />
</Frame>

### Viewing program details

To view details about a program, click any program card.

<Frame>
  <img alt="Program details" />
</Frame>

The program details page includes:

* Product description
* Rewards offered
* Program categories

The page might also include:

* Eligibility requirements
* Additional supporting content to help you understand the partnership

## Joining the Dub Partner Network

Before you can apply to programs in the marketplace, your [partner profile](https://partners.dub.co/profile) needs to be accepted into the Dub Partner Network.

<Frame>
  <img alt="Dub partner network acceptance tasks" />
</Frame>

To submit your Dub Partner Network application, complete all 6 tasks in your [profile page](https://partners.dub.co/profile):

* Upload your logo
* Verify at least 2 social accounts/website
* Fill out your profile description
* Specify your estimated monthly traffic
* Select your preferred earning structures
* Choose your sales channels

Once all tasks are complete, click **Submit application**. Your application will be reviewed by the Dub team.

| Status    | Description                                                                                                                  |
| --------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Submitted | Your Dub Partner Network application is under review. You will be able to apply to marketplace programs once it is approved. |
| Approved  | You can apply to programs in the marketplace and get discovered by brands in the Dub Partner Network.                        |
| Rejected  | Your Dub Partner Network application was not approved. Continue improving your profile and reapply in the future.            |

<Tip>
  A complete, verified profile helps programs understand who you are, where you
  reach your audience, and which partnerships are the best fit for you.
</Tip>

## Applying to a program

After your Dub Partner Network application is approved, click the **Apply** button in the top right of the Program details page.

<Frame>
  <img alt="Program apply button" />
</Frame>

Each program has its own application and enrolment requirements. Fill out the form as accurately as possible to increase your chance of approval.

<Frame>
  <img alt="Program application" />
</Frame>

<Tip>
  Before applying, make sure your profile is up to date and as complete as
  possible. [Learn more](/help/article/partner-profile)
</Tip>

After completing the form, click **Submit application**. You will be notified by email once your application is approved.

## Enrolment status

The program details page and card view will show your enrolment status for each program in the marketplace.

| Status       | Description                                                                                                  |
| ------------ | ------------------------------------------------------------------------------------------------------------ |
| No status    | You have not applied to this program yet.                                                                    |
| Enrolled     | You are approved and enrolled in the program.                                                                |
| Applied      | Your application is under review. You will be notified by email once your application is approved.           |
| Rejected     | Your application was not approved. The program may also decide if you can reapply in the future.             |
| Not eligible | You don't meet the eligibility criteria for this program and cannot apply.                                   |
| Invited      | You have been invited to the program, and can accept the invitation without needing to apply.                |
| Declined     | You have declined the invitation to join the program. You can reapply in the future if you change your mind. |
| Deactivated  | You have been removed from the program, and can reapply in the future.                                       |
| Banned       | You have been banned from the program, and will not be able to reapply in the future.                        |


# Configuring brand and support resources for your partners
Source: https://dub.co/help/article/program-resources

Provide partners support, help docs, program terms of service, and brand assets in their dashboard.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

Set partners up for success by providing support, clear docs, program terms, and brand assets in the dashboard so they can launch faster, avoid mistakes, and represent your brand consistently to drive better performance.

## Where to find your program resources

When viewing your Partner Program navigation, click on the **Resources** page under the **Engagement** group.

<Frame>
  <img alt="Program communication location" />
</Frame>

## Help and support fields

To give your partners the best experience, make sure all fields in your communication settings are filled out:

<Frame>
  <img alt="Help and support fields" />
</Frame>

### Support Email

The email address your partners should use to contact you with questions or issues about your program. **This field is required.**

Your program's support email will be added to all email communications with your partners, and it will also be added to the program support resources section on the partner dashboard sidebar:

<Frame>
  <img alt="Program support resources" />
</Frame>

### Messaging center

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

Enabling this allows you to directly [message your partners](/help/article/messaging-partners) in the Dub platform. This also allows partners to message you as well.

<Frame>
  <img alt="Messaging center" />
</Frame>

### Help Center

Provide the link to any documentation or self-serve resources that can help your partners succeed.

This will also be added to the program support resources section on the partner dashboard sidebar:

<Frame>
  <img alt="Program support resources" />
</Frame>

### Terms of Service

A link to your partner program's Terms of Service (usually separate from your regular platform ToS).

When configured, this will be added to your [program application form](/help/article/program-application-form) as a mandatory checkbox that partners have to accept before submitting their application:

<Frame>
  <img alt="Terms of service checkbox" />
</Frame>

## Brand Assets

Add logo files, colors, or any other documents relating to your brand and the program. These will be available to all your partners from their partner dashboard.

<Frame>
  <img alt="Brand asset fields" />
</Frame>

### Brand logos

Provide the official brand logos so that your partners have the current versions. You can upload JPG, PNG, or SVG files.

<Frame>
  <img alt="Add logo modal" />
</Frame>

### Colors

Provide your official brand colors. You can use the color picker, or you can enter the hex code to select the color.

<Frame>
  <img alt="Add color modal" />
</Frame>

### Additional Files

Add any additional file to support your partners. Common additional files:

* Brand guidelines
* Terms of Service
* Marketing strategies

<Frame>
  <img alt="Add file modal" />
</Frame>


# Real-time events stream
Source: https://dub.co/help/article/real-time-events-stream

Learn more about Dub's real-time events stream feature and how you can use it to gain deeper insights into your engagement data.

<Tip>
  This feature is only available on [Dub's Business
  plan](https://dub.co/pricing) and above.
</Tip>

On any given day, we track upwards of **1,000,000 click events** at Dub.

These events are ingested into our data warehouse, which is then materialized in a comprehensive, [real-time analytics dashboard](/help/article/dub-analytics) that gives you powerful insights into your user base.

While this is great, it is still an aggregated view of your data. What if you want to see a chronological stream of click events and export them for further analysis?

**This is where Dub's Real-time Events Stream feature comes in.**

Dub's Real-time Events Stream is a detailed, real-time stream of events across your entire Dub workspace.

<Frame>
  <img alt="The Events Stream dashboard on Dub" />
</Frame>

## Where can I access it?

The real-time events stream lives in your workspace's [**Events** tab](https://app.dub.co/events), but you can also access it via the **View Events** button in the Analytics tab:

<Frame>
  <img alt="Quick link to open-up the events stream from the Analytics tab" />
</Frame>

<Tip>
  When you click on the **View Events** button, the same filters you have
  applied in the Analytics tab are applied to the Events tab as well.
</Tip>

The events stream is real-time by default – meaning every click event that is recorded will show up in your Events tab in real-time.

## Customizing your events table

You can also customize the dashboard to only show the columns you are interested in:

<Frame>
  <img alt="Selecting which columns to be displayed in the Events table" />
</Frame>

Here are the columns you can choose from:

* **Link**: The link that was clicked/scanned
* **Country**: The country that the event originated from
* **City**: The city that the event originated from
* **Continent**: The continent that the event originated from
* **Device**: The device that was used to trigger the event
* **Browser**: The browser that was used to trigger the event
* **OS**: The operating system that was used to trigger the event
* **Referrer**: The referrer information for the event
* **IP Address**: The IP address that the event originated from (non-EU only)

## Filtering and exporting events

You can also filter the events by the [same filter options as the analytics dashboard](/help/article/dub-analytics#filtering-data).

There are two ways you can filter the events stream:

1. By clicking on the **Filter** button in the top right of the dashboard.
2. Clicking on the filter icon that appears when you hover over a given column value.

<Frame>
  <img alt="Filtering the events stream by link" />
</Frame>

Once you've filtered the events to your liking, you can export them by clicking `⋮` button and selecting **Download as CSV**.

<Frame>
  <img alt="Exporting the filtered events stream to CSV" />
</Frame>


# Connecting your payout method to receive payouts
Source: https://dub.co/help/article/receiving-payouts

Learn how to connect a payout method and start receiving payouts from the affiliate programs you're working with.

Once you've set up a [partner profile on Dub](https://partners.dub.co/), you can connect a payout method to start receiving payouts from the programs you're working with.

Depending on your [country of residence](#supported-payout-countries), you can choose between a [stablecoin wallet](#connecting-a-stablecoin-wallet) or a [bank account](#connecting-a-bank-account) to receive payouts.

|                     | Stablecoin                                 | Bank account                               |
| ------------------- | ------------------------------------------ | ------------------------------------------ |
| Fees                | 0.5% transaction fee                       | No transaction fee                         |
| Payout currency     | USDC                                       | Local currency                             |
| Payout timing       | Within minutes                             | Up to 15 business days                     |
| Supported countries | [Refer below](#supported-payout-countries) | [Refer below](#supported-payout-countries) |

## Connecting a stablecoin wallet

Stablecoin payouts is the easiest and fastest way to receive payouts on Dub (especially for non-US partners):

* **Get paid in USD** – instead of your local currency, you can receive payouts in USDC all around the world
* **Faster payouts** – payouts are sent within minutes instead of having to wait up to 15 business days for regular bank transfers

<Note>Stablecoin payouts are subject to a 0.5% transaction fee.</Note>

<Steps>
  <Step title="Initiate the connect payouts flow">
    First, go to your [payout settings on Dub](https://partners.dub.co/payouts?settings=true) and select "Connect stablecoin wallet":

    <Frame>
      <img alt="Payout settings stablecoin" />
    </Frame>

    This will open up the stablecoin wallet onboarding flow (`accounts.stripe.com/r/acct_xxx...`).
  </Step>

  <Step title="Connect your stablecoin wallet">
    Then, follow the steps in the onboarding flow to connect your stablecoin wallet. You can either use the 1-click connect flow (recommended), or manually input your stablecoin wallet details.

    <Warning>
      Make sure to triple-check that you've entered the correct stablecoin wallet
      address and network to receive USDC payouts. Incorrect details may result in
      payout failures and lost funds.
    </Warning>

    <ImageCarousel />
  </Step>
</Steps>

## Connecting a bank account

Dub partners with Stripe for secure global payouts. To receive payouts directly to your bank account, you'll need to follow these steps to set up a [Stripe Express](https://support.stripe.com/express) account and connect your bank account:

<Steps>
  <Step title="Initiate the connect payouts flow">
    First, go to your [payout settings on Dub](https://partners.dub.co/payouts?settings=true) and select "Connect bank account":

    <Frame>
      <img alt="Payout settings bank account" />
    </Frame>

    This will open up the Stripe Express account onboarding flow (`connect.stripe.com/setup/e/acct_xxx...`).
  </Step>

  <Step title="Create Stripe Express account">
    Then, follow the steps in the onboarding flow to create your Stripe Express account:

    <ImageCarousel />

    <Tip>
      If you have any questions about the Stripe Express account creation flow, you
      can try the following:

      * Refer to the official [Stripe Express help articles](https://support.stripe.com/express)
      * Reach out to [Stripe Express support](https://support.stripe.com/?contact=true)
      * Reach out to [Dub support](https://dub.co/contact/support)
    </Tip>
  </Step>

  <Step title="Connect your bank account">
    For the final step, you'll be asked to connect your bank account to receive payouts. You can either connect your account using Stripe's [Financial Connections](https://stripe.com/financial-connections) flow, or by manually inputting your bank account details.

    <ImageCarousel />

    <Warning>
      **Your payout bank account must match your local currency for compliance
      reasons.** E.g. if you're based in the UK, you will need to connect a GBP bank
      account to receive payouts.

      Related FAQ: ["Can I receive payouts in USD?"](#can-i-receive-payouts-in-usd)
    </Warning>
  </Step>
</Steps>

## Default payout method

If you reside in a [country](#supported-payout-countries) that supports multiple payout methods, you can select a default payout method if more than one has been added.

From the payout settings panel, click the payout account dropdown and select **"Set as default"** on the payout method you want to set as your default:

<Frame>
  <img alt="Selecting your default payout method" />
</Frame>

After your default payout method is set, you'll also receive an email informing you of the change.

## Supported payout countries

Here's the full list of countries that we support sending payouts to, along with the available payout methods for each of them:

<PayoutSupportedCountries />

## FAQs

### My country is not supported by Dub, what should I do?

If you're not in a [supported country](#supported-payout-countries), one workaround is to create a legal entity in any of the supported countries above and use that to receive payouts instead.

For example, you can create a US LLC (using something like [Stripe Atlas](http://stripe.com/atlas)), which would let you receive payouts in USD.

Once you have an LLC ready, go to your [partner profile settings](https://partners.dub.co/settings) and update:

* **Profile Type** – Set to "Company"
* **Legal company name** – Enter your LLC's legal name

Your profile country is based on your current location and cannot be changed in the dashboard. After setting up your legal entity, [contact support](https://dub.co/contact/support) so we can update your country to match (e.g. United States).

<Frame>
  <img alt="update partner profile" />
</Frame>

### Can I receive payouts in USD?

If you're receiving payouts via [bank account](#connecting-a-bank-account), we can only send payouts in your local currency for compliance reasons. For example, if you are based in Australia, you will receive payouts in AUD.

To receive payouts in USD, we recommend switching to [stablecoin payouts](#connecting-a-stablecoin-wallet) instead, which not only lets you receive payouts in USDC, but is also a [lot faster and cheaper](#connecting-a-stablecoin-wallet).

### Can I connect a savings account instead of a checking account?

While it is possible to connect your savings account instead of a checking account, we highly recommend against it for two main reasons:

1. Some banks do not allow third-party deposits into savings accounts, which may cause your payout to fail;
2. Payouts sent to savings accounts can take significantly longer to process (up to 30 business days, depending on the bank), resulting in payout delays.

### Can I connect to my existing Stripe account instead of creating a new one?

Since we use [Stripe Express](https://support.stripe.com/express) for partner payouts, you can only connect an existing Stripe Express account to Dub (and *not an existing Stripe account*).

Here's the difference between the two:

* Stripe Express – login on `connect.stripe.com`
* Regular Stripe accounts – login on `dashboard.stripe.com`

To connect an existing Stripe Express account to Dub, make sure your partner account email matches your Stripe Express account email.

### Do I need to manually withdraw my earnings from Dub?

Unlike platforms like [PartnerStack](https://dub.co/compare/partnerstack), where you have to manually withdraw your earnings to your bank account – you do not need to manually withdraw your earnings on Dub.

Instead, your earnings/payouts are **automatically deposited into your connected bank account** the moment they are paid by the company.

### What is the minimum withdrawal amount, and how does it work?

All payouts above `$10` will be automatically deposited into your bank account as soon as the program processes its payment.

If your payouts are below `$10`, you can still withdraw them for a `$0.50` fee. Go to your [Payouts page](https://partners.dub.co/payouts) and click on "Pay out now" in the "Processed" box to withdraw your payout.

<Frame>
  <img alt="Pay Out Now Button" />
</Frame>


# Risk monitoring
Source: https://dub.co/help/article/risk-monitoring

Safeguard your partner program by automatically flagging, reviewing, and resolving suspicious activity.

<Tip>
  This feature is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

Dub's Risk Monitoring feature helps you catch and take action on any potentially fraudulent or compliance activity in your program. We give you the details for each event so you can make the best decision for your program and customers.

<Frame>
  <img alt="Risk table view" />
</Frame>

The table view contains the following columns:

* **Event:** The specific unique risk event associated with a partner.
* **Partner:** The partner associated with the risk event.
* **Last Detected:** The date and time of the most recent occurrence of this risk event.

Each row is a single event type for a given partner and can contain multiple event occurrences within it. There may be times when you see multiple rows for the same partner, and in this scenario, the partner has been flagged for multiple events that require your review.

## Risk event types

Here are the event types you might see flagged and detected in your program.

### Duplicate account detected

This partner was flagged by our system for having 2 or more Dub accounts. Please review to prevent abuse of program restrictions, caps, or bonuses.

If the partner is legitimate and has multiple accounts, it's recommended they [merge their accounts](/help/article/merging-partner-accounts#merging-your-partner-accounts) to prevent any future issues. If the accounts are not merged, this can be a sign of self-referral, which is when a partner refers themselves to earn commissions or rewards.

### Matching customer email

This partner's email address matches with a customer's email address in your system. This can be a sign of self-referral, which is when a partner refers themselves to earn commissions or rewards.

### Suspicious customer email domain

This partner has referred a customer with an email address that uses a suspicious domain, such as a disposable or temporary email provider. This can be a sign of fraudulent activity, as these types of email addresses are often used to create fake accounts or to hide the identity of the referrer.

### Banned referral source

This partner has traffic coming from a referral source that you've banned in your [risk rules](#risk-rules). This can be a violation of your program's terms if you don't allow traffic from certain sources, and can also be a sign of fraudulent activity if the partner is running unauthorized ads to generate commissions.

### Paid traffic

This partner has traffic coming from paid sources like Google Ads, Facebook Ads, etc. and can be a violation of your program's terms. It can also be a sign of fraudulent activity if the partner is running unauthorized ads to generate commissions.

We recommend getting clarification from the partner before banning them from your program. To make this easier, you can click the **Request details** button to auto-generate a message to the partner.

<Frame>
  <img alt="Paid traffic event details" />
</Frame>

Click **Send** when you're ready to send the message, and then wait for the partner's response before making a decision on whether to ban or resolve the event.

<Frame>
  <img alt="Request details" />
</Frame>

### Network-level ban

This partner was banned from one-or-more programs on Dub. Our risk team has reviewed this decision and confirmed the fraudulent behavior or terms of service violation.

## Reviewing risk events

<Warning>
  We recommended reviewing the risk events thoroughly and potentially reaching out to the partner before making a decision.

  All unresolved risk events expire after 30 days, at which point they will be automatically removed from your Risk Center.
</Warning>

Click on any table row to open the review panel. When reviewing events, there are three panels of information for each item.

<Frame>
  <img alt="Risk review" />
</Frame>

<Steps>
  <Step title="Partner information">
    This is the partner associated with the event. From this section, you can click to message them or to view their profile.

    <Frame>
      <img alt="Event partner" />
    </Frame>
  </Step>

  <Step title="Event details">
    Here you'll see the event type, description, and a line item for each flagged event for the same type associated with this partner.

    <Frame>
      <img alt="Event type" />
    </Frame>
  </Step>

  <Step title="Associated commissions">
    If this partner has any pending commissions, you'll see the list of all commissions on hold. These are any commissions that have occurred since the flagged event. [Learn more here](#associated-commissions).

    <Frame>
      <img alt="Event commissions" />
    </Frame>
  </Step>
</Steps>

## Resolving risk events

When reviewing events, we recommend taking your time and ensuring a proper investigation is done. This could involve:

1. Viewing multiple partner/customer profiles to verify if they are duplicates/self-referrals.
2. Reviewing your internal sales information for any fraud signals (e.g. via [Stripe Radar](https://stripe.com/radar)).
3. Reaching out to the partner in question via our [built-in messaging center](/help/article/messaging-partners) to get clarification on their referral activity.

<Frame>
  <img alt="Resolving events" />
</Frame>

Then, once you've confirmed the veracity of the reports, you can move on to the
next step.

### Associated commissions

For commissions that are on hold for this partner, you can perform the following:

* Click **Mark all as fraud** to mark all commissions on hold as fraud and cancel them.
* You can individually change the commission status to `Fraud` by clicking the `⋯` button and selecting **Edit commission**.
* When [resolving](#resolve-risk-event) the event, any commissions not marked as `Fraud` will move to `Pending`
* When [banning](#ban-partner) the partner, this will change any `On hold` commissions to `Canceled`.

### Resolve risk event

If a risk event is deemed to be benign, you can select the **Resolve event** option to mark the event as safe and keep the partner in your program.

When confirming the decision, you're able to provide optional additional notes about the steps taken to confirm the details, or any other information.

If you trust this partner completely, you're also able to **Exclude from risk monitoring**. When enabled, any future risk events won't be flagged for this partner. This can be changed anytime in the partner's [advanced settings](/help/article/managing-program-partners#advanced-settings).

<Frame>
  <img alt="Resolving" />
</Frame>

### Ban partner

If the risk event turns out to be malicious, you can select the **Ban partner** option to mark the event as fraudulent and ban the partner from your program.

If the partner has other risk events for review, those events will automatically be resolved as well when the partner is banned.

<Frame>
  <img alt="Ban partner" />
</Frame>

A reason must also be selected from the following options:

| Reason                              | Definition                                                                                      |
| ----------------------------------- | ----------------------------------------------------------------------------------------------- |
| Terms of Service Violation          | Partner violated your program rules or Dub’s terms of service.                                  |
| Inappropriate or Offensive Content  | Partner associated your brand or program link with harmful, explicit, or inappropriate content. |
| Artificial Traffic Generation       | Partner used bots, fake clicks, or other methods to inflate traffic.                            |
| Fraudulent Activity                 | Partner generated fake clicks, leads, or other tracked actions to earn rewards.                 |
| Spam or Misleading Content          | Partner used deceptive messaging, false claims, impersonation, or unsolicited promotion.        |
| Brand Abuse or Trademark Violations | Partner misused your brand name, trademarks, or created confusion.                              |

### Reporting fraud events

When banning a partner, you may want to report them to Dub for further investigation or a network ban. To do this, click the toggle **Flag partner for potential fraud**, and provide as much detail as you can about the event.

Our fraud team will review your submission, and if deemed fraudulent, will ban the partner across the entire network and take necessary action to prevent further abuse.

<Frame>
  <img alt="Report partner to Dub" />
</Frame>

## Resolved risk events

To view your resolved events, click the `⋮` dropdown in the top right and select **Resolved events**.

<Frame>
  <img alt="Resolved events" />
</Frame>

Click on any of the resolved items to see the event details, who resolved the event, and any additional notes.

## Risk rules

We give you the option to customize risk signal detection settings on a program-level, as well as choose which [risk event types](#risk-event-types) you want to receive alerts for.

To edit these, click the **Risk rules** button in the top right.

<Frame>
  <img alt="Risk rules button" />
</Frame>

### Paid traffic

If your program doesn't allow traffic from advertisers, ensure this setting is enabled.

<Frame>
  <img alt="Risk rules paid traffic" />
</Frame>

When enabled, it will flag all partner traffic that has been generated from several sources, including:

* Google
* Facebook
* X
* Bing
* LinkedIn
* Reddit
* TikTok

#### Google campaign IDs whitelist

Add your internal Google Campaign IDs here to prevent your own Google Ads traffic from being flagged as fraudulent and associated with a partner.

<Frame>
  <img alt="Google campaign whitelist" />
</Frame>

<Tip>
  **Why this matters for Dynamic Search Ads (DSA):** If you run DSA campaigns, Google crawls your site and automatically selects landing pages to serve as ads. Sometimes Google indexes pages that already contain affiliate tracking parameters (e.g., `?via=affiliate`) from affiliate links being shared around the web.

  When these pages are served as ads, the affiliate parameter carries through – making it appear that an affiliate is running unauthorized paid ads when it's actually your own campaign. Whitelisting your Google Campaign IDs ensures this internal traffic is correctly attributed to you and not flagged as partner fraud.
</Tip>

### Referral source

If you want to flag traffic from a specific URL, enable this setting and then add the URLs you wish to monitor.

<Frame>
  <img alt="Referral settings" />
</Frame>

When adding URLs, you can also use `*` wildcards to match any part of a domain. These a great when monitoring for a specific page and not all traffic from the specific URL.

### Disabling risk settings

If you don't want to receive alerts for a specific risk event type, you can disable it by toggling the switch to the right of the event type.

<Frame>
  <img alt="Risk rules" />
</Frame>

When you disable any of the current active settings, you're also given the option to mark and pending events that match that criteria as "Resolved".

To do this, check the box labelled "Mark all pending events for this rule as `Resolved`" in the confirmation modal.

<Frame>
  <img alt="Setting confirmation" />
</Frame>


# How to set a not found URL for your custom domains
Source: https://dub.co/help/article/setting-not-found-url

Learn more about how you can set a custom not found URL for your domains on Dub.

<Tip>
  This feature is only available on [Pro plans and
  above](https://dub.co/pricing).
</Tip>

By default, when a user visits a short link that doesn't exist on Dub, they will see a placeholder "Link Not Found" page:

<Frame>
  <img alt="Placeholder 404 page on Dub" />
</Frame>

If you are [using a custom domain on Dub](/help/article/how-to-add-custom-domain), you can set a custom URL to redirect users to when a short link under your domain is not found.

To set a custom not found URL for your domains, you can follow the steps below:

1. Go to your [workspace Domains settings tab](https://app.dub.co/domains).
2. Click on the `⋮` button next to the domain you want to set a custom not found URL for.
3. Select **Edit Domain** to edit the domain settings.
4. Toggle the **Not Found URL** section to enable it, and enter the custom URL to redirect users to when a short link under your domain is not found.
5. Click on the **Save changes** button to save the changes.

<Frame>
  <img alt="Setting a not found URL for a custom domain" />
</Frame>


# Setting up your program
Source: https://dub.co/help/article/setting-up-your-program

Step-by-step guide to go from zero to a live partner program on Dub.

<Tip>
  This feature is only available on [Business plans and
  above](https://dub.co/pricing/partners).
</Tip>

In this guide, we will walk you through step-by-step how to set up your partner program on [Dub Partners](https://dub.co/partners) and start growing your affiliate revenue.

## The flow at a glance

<Steps>
  <Step title="Getting started">
    Set up your program name, logo, and referral link settings.
  </Step>

  <Step title="Configure rewards">
    Choose your primary reward type, structure, duration, and amount.
  </Step>

  <Step title="Invite partners">Invite your first partners to your program.</Step>

  <Step title="Help and support">
    Add support resources (support email, terms of service, help center) that
    partners see on their dashboard.
  </Step>

  <Step title="Overview">Review your selection and create your program.</Step>
</Steps>

<Note>
  Migrating from another platform? Check out our [migration
  guides](/help/article/migrating-from-rewardful) or [contact
  support](https://dub.co/contact/support).
</Note>

## Step 1: Getting started

**Goal**: Set up your program name, logo, and [referral link settings](/help/article/partner-link-settings).

<Frame>
  <img alt="Getting started step in Create partner program" />
</Frame>

1. **Company name**: The name of your company (e.g., "Acme").
2. **Logo**: Upload a square logo. It appears on your program landing page and in partner materials.
3. **Referral link**: Set the domain and destination for partner links:
   * **Program domain**
     * **Claim a free .link domain**: No setup. Free for one year with your paid account.
     * **Connect a domain you own** DNS setup required. Use a domain dedicated to your program (e.g., `refer.yourcompany.com`).
   * **Website URL**: Where visitors go when they click a partner's referral link (e.g., `https://yourcompany.com`).

Click **Continue** to go to the next step.

## Step 2: Configure rewards

**Goal**: Choose your primary reward type, structure, duration, and amount.

<Frame>
  <img alt="Configure rewards step in Create partner program" />
</Frame>

1. **Reward type**
   * **Sale**: When customers subscribe to your product (most common for SaaS).
   * **Lead**: For sign-ups and demos.

2. **Commission structure (only for sale rewards)**
   * **Recurring**: Ongoing revenue share (e.g., 15% of each payment for 12 months)
   * **One-off**: Fixed payout per conversion (e.g., \$20 per sale)

3. **Duration (only for recurring rewards)**: How long partners earn (e.g., 12 months)

4. **Reward amount**:
   * **Percentage**: Share of revenue (e.g., 15% per sale)
   * **Flat**: Fixed amount per conversion (e.g., \$20 per lead)

Select your reward conditions and click **Continue**.

<Tip>
  You can add more [reward conditions](/help/article/partner-rewards) and
  [partner groups](/help/article/partner-groups) later in your program settings.
</Tip>

## Step 3: Invite partners

**Goal**: Invite your first partners to your program.

<Note>
  You can skip this step and [invite partners
  later](/help/article/inviting-partners) from the Partners tab.
</Note>

<Frame>
  <img alt="Invite partners step in Create partner program" />
</Frame>

1. Enter partner **email addresses**.
2. Click **+ Add partner** to add each one.
3. Click **Continue** to proceed (you can also skip and invite later from the Partners tab).

## Step 4: Help and Support

**Goal**: Add support resources (support email, terms of service, help center) that partners see on their dashboard.

<Frame>
  <img alt="Help and Support step in Create partner program" />
</Frame>

1. **Support email (required)**: Email partners can reach you for questions.
2. **Help center URL**: Link to your help docs or FAQ (e.g. `https://yourcompany.com/help`).
3. **Terms of Service URL**: Link to your affiliate/partner terms (e.g. `https://yourcompany.com/legal/affiliates`).

These appear on the partner dashboard. You can add or update them later in your [program resources tab](/help/article/program-resources).

***

## Step 5: Overview

**Goal**: Review your selection and create your program.

<Frame>
  <img alt="Overview step in Create partner program" />
</Frame>

You'll see a summary of:

* **Reward**: e.g. "Earn 15% for the first sale"
* **Referral link type**: e.g. `refer.dub.co/{partner-link-key}`
* **Website URL**: Where partner links redirect

Use the pencil icon on each card to edit. When everything looks correct, click **Create program**.

***

## What's next?

<CardGroup>
  <Card title="Set up conversion tracking" icon="arrow-progress" href="/docs/quickstart/server">
    Track conversions and attribute sign-ups and purchases
  </Card>

  <Card title="Connect a bank account" icon="bank" href="/help/article/how-to-set-up-bank-account">
    Set up a bank account for sending partner payouts
  </Card>

  <Card title="Create partner groups" icon="users" href="/help/article/partner-groups">
    Segment partners by rewards, discounts, and performance
  </Card>

  <Card title="Invite your partners" icon="user-plus" href="/help/article/inviting-partners">
    Add partners via dashboard, API, or application form
  </Card>

  <Card title="Configure partner rewards" icon="money-bill" href="/help/article/partner-rewards">
    Create flexible reward structures for partners
  </Card>

  <Card title="Embedded referral dashboard" icon="grid-2" href="/docs/partners/embedded-referrals">
    Enroll partners directly from wihin your app
  </Card>
</CardGroup>


# How to create public analytics dashboards on Dub
Source: https://dub.co/help/article/share-analytics

Learn how to create public analytics pages for your Dub short links and link folders – with password protection and search engine indexing options.

On Dub, you can create a public analytics dashboard for your short links or link folders.

This allows you to share the analytics for a given short link (or a folder of links) with clients or other external stakeholders without having to invite them to your Dub workspace.

For further control, you can also show [conversion analytics](#show-conversion-data), [set a password](#setting-a-password), or [enable search engine indexing](#search-engine-indexing) for the dashboard.

In this guide, we'll go over how to set up public analytics dashboards and the options available.

## Creating a shared analytics dashboard for a short link

There are two ways to enable a public analytics dashboard for a given short link:

**Option 1**: On the links dashboard, hover over the `perfomance` counter button for a given short link and click on the **Share dashboard** button.

<Frame>
  <img alt="More options dropdown menu on the links dashboard" />
</Frame>

**Option 2**: On the analytics page for a given short link, click on the **Share** button in the top right corner.

<Frame>
  <img alt="Share button on the link's analytics page" />
</Frame>

Then, inside the **Share Dashboard** modal, toggle the **Enable public sharing** switch to **ON**.

<Frame>
  <img alt="Share short link dashboard modal" />
</Frame>

Behind the scenes, Dub creates a public page for your link's analytics and automatically copies the link to your clipboard. [Here's an example](https://d.to/stats/try).

<ImageLink alt="Dub's Analytics dashboard" href="https://d.to/stats/try" />

## Creating a shared analytics dashboard for a link folder

<Tip>Only workspace owners can create shared dashboards for link folders.</Tip>

There are two ways to enable a public analytics dashboard for a given link folder:

**Option 1**: On the folder view, click the `⋮` dropdown and select **Share analytics**.

<Frame>
  <img alt="Folder dropdown options" />
</Frame>

**Option 2**: On the analytics page for a given link folder, click on the **Share** button in the top right corner.

<Frame>
  <img alt="Share button on the folder's analytics page" />
</Frame>

Then, inside the **Share Dashboard** modal, toggle the **Enable public sharing** switch to **ON**.

<Frame>
  <img alt="Share folder dashboard modal" />
</Frame>

## Sharing settings

The following settings are available for both short links and link folders.

### Show conversion analytics

If you enable Conversions analytics, this will change what's visible on the shared dashboard to include your lead and sales analytics.

Here's how the dashboard looks with conversion analytics enabled:

<Frame>
  <img alt="Shared folder dashboard with conversion analytics" />
</Frame>

Here's how the dashboard looks without conversion analytics enabled:

<Frame>
  <img alt="Shared folder dashboard with no conversion analytics" />
</Frame>

### Setting a password

You can also set a password for your public analytics dashboard to further secure the page. To do that, toggle the **Password protection** switch to **ON** and set a password in the field below.

<Frame>
  <img alt="Share Dashboard modal with password protection enabled" />
</Frame>

Then, when someone visits the public analytics dashboard, they will be prompted to enter the password in order to view the page.

<Frame>
  <img alt="Public analytics dashboard with password protection enabled" />
</Frame>

### Search engine indexing

If you want to enable search engine indexing for your public analytics dashboard, toggle the **Search engine indexing** switch to **ON**.

This will make the page more discoverable by search engines like Google.

Otherwise, public analytics dashboards are served with a `noindex` meta tag by default, which tells search engines not to index the page.

`html <meta name="robots" content="noindex, nofollow" />`

## How to disable a public analytics dashboard

Follow the same steps as the ["How to create a public analytics dashboard"](#how-to-create-a-public-analytics-dashboard) section above, but toggle the **Enable public sharing** switch to **OFF**.

Dub will automatically disable the public analytics page for the given link. You will still be able to view the analytics for the link on your Dub dashboard, but the public page will no longer be accessible.

<Warning>
  Warning: By disabling a public analytics dashboard for a given link, your
  existing dashboard link will break and you'll have to create a new one – which
  will have a different URL. This action is irreversible.
</Warning>


# Troubleshooting custom domains
Source: https://dub.co/help/article/troubleshooting-domains

Learn how to troubleshoot common issues with custom domains on Dub.

When you're [setting up a custom domain on Dub](/help/article/how-to-add-custom-domain), you might run into some issues. Here are some common ones:

## domain.com redirected you too many times (ERR\_TOO\_MANY\_REDIRECTS)

<Frame>
  <img alt="Browser error message saying 'dub.engineering redirected you too many times'" />
</Frame>

If you're getting a `domain.com redirected you too many times` error in your browser, chances are you're [using Cloudflare for your domain hosting](/help/article/using-cloudflare-domains) and are missing an extra configuration step to make this work.

To fix this, all you have to do is set your domain to use [Cloudflare DNS only](/help/article/using-cloudflare-domains#using-cloudflares-dns-only-recommended) (without Cloudflare Proxy).

## This site can't provide a secure connection (ERR\_SSL\_PROTOCOL\_ERROR)

<Frame>
  <img alt="Browser error message saying 'This site can't provide a secure connection'" />
</Frame>

Another common error is when the `https` version of your domain doesn't work (but the `http` version does). When that happens, you'll get a **This site can't provide a secure connection** error in your browser.

The reason for this error is that the SSL certificate for your domain has not been generated yet, and it should automatically be resolved anywhere between 1 to 24 hours. If your domain still doesn't work after 24 hours, please [reach out to us](https://dub.co/contact/support) and we'll be happy to help you out.


# Using Cloudflare domains with Dub
Source: https://dub.co/help/article/using-cloudflare-domains

Learn how to use Cloudflare domains for your Dub workspace

If you're using a domain on Cloudflare for your Dub workspace, you might need to configure a few extra steps to make sure that your domain works properly.

## Using Cloudflare's DNS only (recommended)

We recommend using Cloudflare's DNS only mode for your custom domain. This makes sure that requests/clicks to your short links will always reach Dub directly instead of being served through Cloudflare – which can lead to [inaccurate analytics data](/help/article/incorrect-analytics-geolocation-data).

Configuring Cloudflare as DNS only is also better for redirect speeds, since you avoid having your users' requests get proxied through Cloudflare, which adds additional latency.

To set this up, all you need to do is [set your domain's DNS records](/help/article/how-to-add-custom-domain#step-2-configure-your-domain) to point to Dub's servers and make sure that you're using the **DNS only** option on Cloudflare.

1. Go to your Cloudflare dashboard and select your domain.
2. Click on the **DNS** tab on the sidebar menu.
3. Under **Records**, click on the **Edit** button.
4. Change the **Proxy status** to **DNS only**.
5. Click on the **Save** button.

<Frame>
  <img alt="" />
</Frame>

This will make sure that the traffic to your domain is *not proxied through Cloudflare* and is sent directly to Dub.

## Using Cloudflare's Proxy

If you really want to use Cloudflare's proxy for your custom domain, you'll need to make sure that your SSL/TLS encryption mode is set to "Full" on Cloudflare.

<Frame>
  <img alt="Using Cloudflare's proxy for your custom domain" />
</Frame>

You can do that by following these steps:

1. Go to your Cloudflare dashboard and select your domain.
2. Click on the **SSL/TLS** tab on the sidebar menu.
3. Under **Overview**, click on the **Configure** button.
4. Select the **Custom SSL/TLS** option and then select the **Full** option.
5. Click on **Save** to apply the changes.

<Frame>
  <img alt="" />
</Frame>

<Warning>
  Using Cloudflare's Proxy can potentially **cause [inaccuracies in analytics
  data](/help/article/incorrect-analytics-geolocation-data)**. This is because using a proxy will send all traffic through the proxy
  first, then to Dub. This will result in incorrect geolocation data being
  presented and the public IP address of your proxy being sent.

  To avoid this, we recommend using [Cloudflare's DNS only mode](#using-cloudflares-dns-only-without-proxy) instead of using
  their proxy.
</Warning>


# Can I use a proxy on top of my Dub custom domain?
Source: https://dub.co/help/article/using-proxies-on-custom-domains

We do not recommend using a proxy (e.g. Cloudflare, Cloudfront) on top of your Dub custom domain. Here are a few reasons why.

When [using a custom domain on Dub](/help/article/how-to-add-custom-domain), we do not recommend using a proxy (e.g. Cloudflare Proxy, Cloudfront CDN) in front of your custom domain.

Here are a few reasons why:

1. [**Inaccurate analytics data**](/help/article/incorrect-analytics-geolocation-data): The proxy will send all your click traffic through the proxy first, then to Dub. This will result in incorrect geolocation data being presented and the public IP address of your proxy being sent.
2. **Increased redirect latency**: If you're using a proxy, all requests to your links will have to pass through the proxy before reaching Dub, which can result in increased redirect latency, ranging from 2x to 10x slower link redirects.
3. **Reduced reliability**: By adding a proxy in front of your links, you're essentially introducing an external dependency that can cause your links to go down if the proxy fails.
4. **Link redirect issues**: Proxies can also lead to [ERR\_TOO\_MANY\_REDIRECTS issues](/help/article/troubleshooting-domains#domaincom-redirected-you-too-many-times-err_too_many_redirects), which will break your redirects and result in a subpar user experience.

## How do I know if my domain is using a proxy?

If you're [using Cloudflare for your domains](/help/article/using-cloudflare-domains), you can use [this tool](https://checkforcloudflare.selesti.com/) to check if you're using Cloudflare Proxy. And [here's how you can turn off Cloudflare Proxy for your domains](/help/article/using-cloudflare-domains).

For all other DNS/proxy providers, you can use a tool like [digger.tools](https://digger.tools/) to check if your domain is using the [correct DNS records](/help/article/how-to-add-custom-domain#step-2-configure-your-domain).


# How do I add UTM parameters to a link?
Source: https://dub.co/help/article/utm-builder

Learn more about why UTM parameters are important for marketing links and how to add them to your links using the Dub UTM Builder.

UTM (short for *Urchin Tracking Module*) parameters are great for marketing links because they provide detailed tracking and analytics. Here’s why:

* **Source Identification**: They specify the source of traffic (e.g., Facebook, Google), helping to identify which platforms are driving traffic.
* **Campaign Tracking**: They track specific marketing campaigns, allowing marketers to evaluate the effectiveness of each campaign.
* **Medium Differentiation**: They distinguish between different types of marketing mediums (e.g., email, social media, PPC).
* **Content Performance**: They track different content pieces within a campaign, providing insights on what content resonates best with the audience.
* **ROI Measurement**: They help in calculating the return on investment (ROI) for different marketing efforts.

<Tip>
  Learn more about the best practices when it comes to UTM tracking with our
  [Ultimate Guide to UTM Tracking](https://dub.co/blog/utm-guide).
</Tip>

## Dub's UTM Builder

To make it easier to add UTM parameters to your links, Dub provides a UTM builder that allows you to easily populate UTM parameters for your links. [Here's a live demo](https://dub.co/tools/utm-builder) that you can play around with.

Here are the UTM parameters that Dub supports:

| Parameter        | Code           | Description                                                |
| ---------------- | -------------- | ---------------------------------------------------------- |
| **UTM Source**   | `utm_source`   | The source of your traffic (e.g. Facebook, Twitter, etc.). |
| **UTM Medium**   | `utm_medium`   | The medium of your traffic (e.g. social, email, etc.).     |
| **UTM Campaign** | `utm_campaign` | The name of your campaign (e.g. summer2023)                |
| **UTM Term**     | `utm_term`     | The term of your campaign (e.g. running+shoes)             |
| **UTM Content**  | `utm_content`  | The content of your campaign (e.g. logo+link)              |
| **Referral**     | `ref`          | The website that is sending traffic to your link.          |

As you can see, we also support an increasingly common parameter `ref` – as popularized by sites like [Product Hunt](https://www.producthunt.com/) and [OSS Gallery](https://oss.gallery/).

## How to use the Dub UTM Builder

To use the Dub UTM builder, follow these steps:

<Steps>
  <Step title="Create a link">
    First, [create a link](/help/article/how-to-create-link) using the [Dub link builder](/help/article/dub-link-builder).

    <Frame>
      <img alt="The Dub link builder" />
    </Frame>
  </Step>

  <Step title="Open the UTM modal and add your UTM params">
    Click on the **UTM** button in the link builder. This will open the **UTM Builder** modal.

    You can also quickly access the **UTM Builder** feature by using the keyboard shortcut `U` while in the link builder.

    <Frame>
      <img alt="Use the UTM builder to populate UTM parameters for your links" />
    </Frame>

    Here, you can populate the UTM parameters for your link. Some example values include:

    * **UTM Source**: `twitter`
    * **UTM Medium**: `social`
    * **UTM Campaign**: `summer2023`

    Click on the **Save** button to add the UTM parameters to your link.
  </Step>

  <Step title="Create the link">
    Click the **Create link** button to generate the link with the UTM parameters.

    Voila! You've got a link with UTM parameters.
  </Step>
</Steps>

## UTM Templates

Dub also supports the ability to [create UTM templates](/help/article/how-to-create-utm-templates) that you can reuse across your team, which leads to better campaign management & tracking accuracy.

<Frame>
  <img alt="Selecting a UTM template from the dropdown" />
</Frame>

[Read the guide on how to create UTM templates](/help/article/how-to-create-utm-templates) or [try out the demo here](https://dub.co/tools/utm-builder).


# What is a "Workspace" on Dub?
Source: https://dub.co/help/article/what-is-a-workspace

Learn more about what a "Workspace" is on Dub and how you can use it to organize your links.

On Dub, a "Workspace" is a space that you can use to store all the short links for your website, app, or business.

You can think of a Dub workspace as a "Team" or an "Organization" where you can [invite your teammates](/help/article/how-to-invite-teammates) to join and manage the links together.

## How to create a workspace

To create a workspace, go to the [Dub Dashboard](https://app.dub.co).

<Frame>
  <img alt="The workspace creation modal on Dub" />
</Frame>

Here are the fields in the workspace creation modal:

1. **Workspace Name**: This is the name of your workspace on Dub.
2. **Workspace Slug**: This is your workspace's unique slug on Dub.

Once you have filled in the fields, click on the **Create workspace** button to create your workspace.

## Onboarding checklist

After you have created your workspace, you will be redirected to the workspace dashboard. Here, you will see a checklist of things that you can do to get started with your workspace.

<Frame>
  <img alt="Onboarding checklist for a new workspace in Dub" />
</Frame>

1. **Configure your custom domain**: You can [configure your custom domain](/help/article/how-to-add-custom-domain) to use your own domain for your short links.
2. **Create or import your links**: You can [create links](/help/article/how-to-create-link) or [import existing ones](/help/article/how-to-import-csv) to your workspace.
3. **Invite your teammates**: You can [invite your teammates](/help/article/how-to-invite-teammates) to join your workspace and manage the links together.

## How many domains can I add to a workspace?

Dub is the only link management platform that allows you to [add custom domains](/help/article/how-to-add-custom-domain) for free. The number of domains that you can add to a workspace depends on your plan:

* **Free plan**: 3 domains
* **Pro plan**: 10 domains
* **Business plan**: 100 domains
* **Advanced plan**: 250 domains
* **Enterprise plan**: Unlimited domains

If your workspace has multiple domains, you will be able to filter the links in the workspace by their respective domains.

<Frame>
  <img alt="Filtering links by domain in Dub" />
</Frame>

## Limits and Billing for Workspaces

On Dub, you can create as many workspaces as you want, and each workspace will be billed separately.

However, to prevent abuse, you can only create up to 2 workspaces on the Free plan. If you need more workspaces, you can [upgrade to the Pro plan](https://dub.co/pricing).


# Managing workspace members with roles
Source: https://dub.co/help/article/workspace-roles

Learn how to use roles to manage your workspace members' permissions on Dub.

On Dub, you can [invite your teammates](/help/article/how-to-invite-teammates) to join your workspace. This is useful if you want to collaborate with your team on your short links.

When you invite your teammates, it is important to understand that you can manage their permissions and roles in your workspace. This is where roles come in.

There are four roles you can assign to your team members:

## Owner role

The owner of a workspace has the highest level of access. When you create a new workspace, you are automatically assigned the owner role by default. Then, when you invite your teammates, you can assign them either the owner or member role.

## Member role

When you invite your teammates to join your workspace, they are assigned the member role by default.

The member role is limited in what they can do compared to the owner role – this can be useful for security purposes, e.g. you may want to give your team members the ability to manage short links and tags, but not the ability to manage domains or the workspace settings.

## Viewer role

The viewer role is meant for teammates who need visibility into what is happening in the workspace, without the ability to make changes. This is a good fit for stakeholders, clients, or teammates who want to review links, performance, and partner activity, but should not be able to edit, create, or delete anything.

<Tip>
  The Viewer role is only available on [Business plans and
  above](https://dub.co/pricing).
</Tip>

## Billing role

The billing role is intended for teammates who are responsible for [partner payouts](/help/article/partner-payouts) and [managing billing information](/help/article/how-to-change-billing-information). This role gives them access to billing details (e.g. [setting up a bank account for partner payouts](/help/article/how-to-set-up-bank-account)) and payout actions, while keeping the rest of the workspace locked down.

<Tip>
  The Billing role is only available on [Advanced plans and
  above](https://dub.co/pricing/partners).
</Tip>

## Comparison grid

Here's a comparison grid of the features available to workspace members.

| [Dub Partners](https://dub.co/partners)                                   | Owner | Member | Viewer | Billing |
| ------------------------------------------------------------------------- | ----- | ------ | ------ | ------- |
| Access [groups](/help/article/partner-groups)                             | ✅     | ✅      | ✅      | ✅       |
| Create, update, and delete [groups](/help/article/partner-groups)         | ✅     | ✅      | ❌      | ❌       |
| [Confirm payouts](/help/article/partner-payouts#how-to-pay-your-partners) | ✅     | ❌      | ❌      | ✅       |
| View [messages](/help/article/messaging-partners)                         | ✅     | ✅      | ✅      | ✅       |
| Create [messages](/help/article/messaging-partners)                       | ✅     | ✅      | ❌      | ❌       |

| [Dub Analytics](https://dub.co/analytics)     | Owner | Member | Viewer | Billing |
| --------------------------------------------- | ----- | ------ | ------ | ------- |
| View [analytics](/help/article/dub-analytics) | ✅     | ✅      | ✅      | ✅       |

| [Dub Links](https://dub.co/links)                                           | Owner | Member | Viewer | Billing |
| --------------------------------------------------------------------------- | ----- | ------ | ------ | ------- |
| View links                                                                  | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete links                                             | ✅     | ✅      | ❌      | ❌       |
| View [folders](/help/article/link-folders)                                  | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete [folders](/help/article/link-folders)             | ✅     | ✅      | ❌      | ❌       |
| View [tags](/help/article/how-to-use-tags)                                  | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete [tags](/help/article/how-to-use-tags)             | ✅     | ✅      | ❌      | ❌       |
| View [domains](/help/article/how-to-add-custom-domain)                      | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete [domains](/help/article/how-to-add-custom-domain) | ✅     | ❌      | ❌      | ❌       |

| Dub Integrations                                                  | Owner | Member | Viewer | Billing |
| ----------------------------------------------------------------- | ----- | ------ | ------ | ------- |
| View [integrations](https://dub.co/integrations)                  | ✅     | ✅      | ✅      | ✅       |
| Install/Uninstall [integrations](https://dub.co/integrations)     | ✅     | ✅      | ❌      | ❌       |
| View [API keys](/docs/api-reference/tokens)                       | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete [API keys](/docs/api-reference/tokens)  | ✅     | ❌      | ❌      | ❌       |
| View [webhooks](/docs/webhooks/introduction)                      | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete [webhooks](/docs/webhooks/introduction) | ✅     | ❌      | ❌      | ❌       |
| View [OAuth apps](/docs/integrations/quickstart)                  | ✅     | ✅      | ✅      | ✅       |
| Create, update, or delete OAuth apps                              | ✅     | ❌      | ❌      | ❌       |

| [Workspace management](/help/article/what-is-a-workspace)                           | Owner | Member | Viewer | Billing |
| ----------------------------------------------------------------------------------- | ----- | ------ | ------ | ------- |
| View workspace                                                                      | ✅     | ✅      | ✅      | ✅       |
| Update or delete workspace                                                          | ✅     | ❌      | ❌      | ❌       |
| [Manage teammates](/help/article/how-to-invite-teammates)                           | ✅     | ❌      | ❌      | ❌       |
| [Manage billing information](/help/article/how-to-change-billing-information)       | ✅     | ❌      | ❌      | ✅       |
| [Set up bank account for partner payouts](/help/article/how-to-set-up-bank-account) | ✅     | ❌      | ❌      | ✅       |
| Configure SAML SSO                                                                  | ✅     | ❌      | ❌      | ❌       |
| Configure SCIM                                                                      | ✅     | ❌      | ❌      | ❌       |