> ## Documentation Index
> Fetch the complete documentation index at: https://dub.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Client-side tracking

> 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).

Dub's powerful [attribution platform](/docs/concepts/attribution) lets you understand how well your links are translating to actual users and revenue dollars.

![Conversion analytics](https://assets.dub.co/blog/introducing-dub-conversions.webp)

In this guide, we'll learn how to track conversion events with Dub using client-side tracking.

> [!WARNING]
>
> Client-side tracking has 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.
> * **Security issues** – With [Dub Partners](https://dub.co/help/article/dub-partners), leaked publishable keys can lead to fabricated events, which can falsely inflate partner commissions.
>
> For more accurate conversion tracking, consider using [server-side conversion tracking](/docs/quickstart/server)

> [!NOTE]
>
> Want to test your setup before going live? Use a [staging
> workspace](/docs/testing) to track test events without affecting your
> production analytics.

## 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.

> **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.
>
> ![Enabling conversion tracking for a workspace](https://mintcdn.com/dub/JTHh2SZNiQ9ZNd5J/images/conversions/enable-conversion-tracking-workspace.png?fit=max\&auto=format\&n=JTHh2SZNiQ9ZNd5J\&q=85\&s=5c6d1068052e70f2bce9be3ed8af4b0d)
>
> This option will enable conversion tracking in the [Dub Link Builder](/help/article/dub-link-builder) for all future links.

> **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.
>
> ![Enabling conversion tracking for a link](https://mintcdn.com/dub/F9cdc9nB_SI4yl65/images/conversions/enable-conversion-tracking.png?fit=max\&auto=format\&n=F9cdc9nB_SI4yl65\&q=85\&s=4153d4a981e2a13324464ca3d30625cd)
>
> > [!TIP]
> >
> > You can also use the `C` keyboard shortcut when inside the link builder to
> > quickly enable conversion tracking for a given link.

> **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:
>
> ```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,
>   )
> )
> ```

## 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:

**1. 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.

![Enabling conversion tracking for a workspace](https://mintcdn.com/dub/JTHh2SZNiQ9ZNd5J/images/conversions/allowed-hostnames.png?fit=max\&auto=format\&n=JTHh2SZNiQ9ZNd5J\&q=85\&s=6418540c30df8c7c06212e131a88a518)

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!

**2. 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.

![Enabling conversion tracking for a workspace](https://mintcdn.com/dub/JTHh2SZNiQ9ZNd5J/images/conversions/publishable-key.png?fit=max\&auto=format\&n=JTHh2SZNiQ9ZNd5J\&q=85\&s=9d79bcd3e8e3ac51d6b86abe91ca0701)

**3. 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:

> **[React](/docs/sdks/client-side/installation-guides/react)**

> **[Manual installation](/docs/sdks/client-side/installation-guides/manual)**

> **[Framer](/docs/sdks/client-side/installation-guides/framer)**

> **[Shopify](/docs/sdks/client-side/installation-guides/shopify)**

> **[WordPress](/docs/sdks/client-side/installation-guides/wordpress)**

> **[Webflow](/docs/sdks/client-side/installation-guides/webflow)**

> **[Google Tag Manager](/docs/sdks/client-side/installation-guides/google-tag-manager)**

> [!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.

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

## 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).

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.

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

### Track leads from form submissions

You can also track leads directly when users submit a form on your website.

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

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.

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

### Track sales from form submissions

You can also track sales directly when users complete a checkout form on your website.

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

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).

## 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.

![Time-series line chart](https://mintcdn.com/dub/F9cdc9nB_SI4yl65/images/conversions/timeseries-chart.png?fit=max\&auto=format\&n=F9cdc9nB_SI4yl65\&q=85\&s=7380bc6120ade538b2b65eefdc76d3ed)

* **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).

![Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales](https://mintcdn.com/dub/F9cdc9nB_SI4yl65/images/conversions/funnel-chart.png?fit=max\&auto=format\&n=F9cdc9nB_SI4yl65\&q=85\&s=6275caafcfc3be6d8b498149222f225e)

* **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.

![The Events Stream dashboard on Dub](https://mintcdn.com/dub/F9cdc9nB_SI4yl65/images/conversions/events-table.png?fit=max\&auto=format\&n=F9cdc9nB_SI4yl65\&q=85\&s=c2467f9fa2e755f06b3e7b147fa0bd81)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.