跳转到主要内容

介绍

Dub 是一个强大的链接管理平台,帮助您创建、分享和跟踪短链接。通过将 Dodo Payments 与 Dub 集成,您可以在客户完成购买时自动跟踪销售转化事件,使您能够衡量营销活动和推荐计划的投资回报率。 当客户完成以下操作时,Dub 会记录一个 “销售” 事件:
  • 完成一次性付款
  • 订阅付费计划
  • 进行定期订阅付款
此集成需要在您的链接上启用转化跟踪的 Dub 账户。

工作原理

Dub 通过在用户点击您的 Dub 短链接时存储在 cookie 中的唯一点击 ID (dub_id) 来跟踪访客。要将销售归因于您的链接,您需要:
  1. 在创建结账会话时捕获 Dub 的点击 ID,从 dub_id cookie 中获取
  2. 将点击 ID 存储在您的支付元数据中,连同客户的外部 ID
  3. 在支付成功时通过其 Track API 发送销售数据到 Dub
这使得 Dub 能够将成功的销售与原始链接点击匹配,从而为您提供完整的转化归因。

前提条件

在设置此集成之前,请确保您拥有:
  1. 一个 Dub 账户 和一个工作区
  2. 为您的链接启用转化跟踪
  3. 您的 Dub API 密钥(在您的 Dub 仪表板的设置 → API 密钥中可用)

开始使用

1

在 Dub 中启用转化跟踪

在您的 Dub 仪表板中,为您想要跟踪销售的链接启用转化跟踪。这使得 Dub 能够在客户完成购买时记录销售事件。
了解有关在 Dub 文档 中启用转化跟踪的更多信息。
2

获取您的 Dub API 密钥

导航到您的 Dub 仪表板 → 设置 → API 密钥,并创建一个具有 conversions.write 范围的新 API 密钥。
保持您的 API 密钥安全,切勿在客户端代码中暴露它。
3

在结账中捕获点击 ID

在创建结账会话时,从 cookie 中捕获 Dub 点击 ID,并将其添加到您的支付元数据中。
4

通过 Webhook 发送销售数据

配置一个 webhook,在支付成功时将销售数据发送到 Dub 的 Track API。
5

完成!

销售转化事件现在将在您的 Dub 分析仪表板中显示,并完全归因于您的链接。

实施指南

第 1 步:将点击 ID 和客户 ID 添加到结账元数据

在创建结账会话时,从 cookie 中捕获 Dub 点击 ID,并将其与客户的外部 ID 一起包含在您的支付元数据中。
import { cookies } from 'next/headers';
import DodoPayments from 'dodopayments';

const client = new DodoPayments();

export async function createCheckout(productId: string, customerId: string) {
  // Capture Dub click ID from cookie
  const dubClickId = cookies().get('dub_id')?.value;

  const payment = await client.payments.create({
    billing: {
      city: 'New York',
      country: 'US',
      state: 'NY',
      street: '123 Main St',
      zipcode: '10001',
    },
    customer: {
      email: '[email protected]',
      name: 'John Doe',
    },
    product_id: productId,
    metadata: {
      dub_click_id: dubClickId,           // Store Dub click ID
      dub_external_id: customerId,        // Store your customer's unique ID
    },
  });

  return payment;
}

第 2 步:将销售数据发送到 Dub

配置一个 webhook 端点,在支付成功时将销售数据发送到 Dub 的 Track API。
1

打开 Webhook 部分

在您的 Dodo Payments 仪表板中,导航到 Webhooks → + 添加端点 并展开集成下拉菜单。
添加端点和集成下拉菜单
2

选择 Dub

选择 Dub 集成卡片。
3

输入 API 密钥

在配置字段中提供您的 Dub API 密钥。
添加 API 密钥
4

配置转换

编辑转换代码以格式化支付数据以适应 Dub 的 Track Sale API。
5

测试并创建

使用示例有效负载进行测试,然后单击 创建 以激活集成。

转换代码示例

基本销售跟踪

在支付成功时跟踪销售:
basic_sale.js
function handler(webhook) {
  if (webhook.eventType === "payment.succeeded") {
    const payment = webhook.payload.data;

    // Only send to Dub if click ID exists in metadata
    if (payment.metadata && payment.metadata.dub_click_id) {
      webhook.payload = {
        clickId: payment.metadata.dub_click_id,
        externalId: payment.metadata.dub_external_id || payment.customer.customer_id,
        amount: payment.total_amount, // Ensure the amount is in cents
        currency: payment.currency || "USD",
        paymentProcessor: "dodo",
        invoiceId: payment.payment_id,
        metadata: {
          customer_email: payment.customer.email,
          customer_name: payment.customer.name,
          product_id: payment.product_cart ? payment.product_cart.map(product => product.product_id).join(', ') : undefined,
        },
      };
    } else {
      // Cancel dispatch if no click ID (organic traffic)
      webhook.cancel = true;
    }
  }
  return webhook;
}

跟踪订阅销售

跟踪初始订阅和定期付款:
subscription_sale.js
function handler(webhook) {
  const data = webhook.payload.data;

  // Track initial subscription activation
  if (webhook.eventType === "subscription.active") {
    if (data.metadata && data.metadata.dub_click_id) {
      webhook.payload = {
        clickId: data.metadata.dub_click_id,
        externalId: data.metadata.dub_external_id || data.customer.customer_id,
        amount: data.recurring_pre_tax_amount, // Amount in cents
        currency: data.currency || "USD",
        paymentProcessor: "dodo",
        invoiceId: data.subscription_id,
        eventName: "Subscription Started",
        metadata: {
          subscription_id: data.subscription_id,
          product_id: data.product_id,
          billing_interval: data.payment_frequency_interval,
          customer_email: data.customer.email,
        },
      };
    } else {
      // Cancel dispatch if no click ID (organic traffic)
      webhook.cancel = true;
    }
  }

  // Track recurring subscription payments
  if (webhook.eventType === "subscription.renewed") {
    if (data.metadata && data.metadata.dub_click_id) {
      webhook.payload = {
        clickId: data.metadata.dub_click_id,
        externalId: data.metadata.dub_external_id || data.customer.customer_id,
        amount: data.recurring_pre_tax_amount,
        currency: data.currency || "USD",
        paymentProcessor: "dodo",
        invoiceId: `${data.subscription_id}_${Date.now()}`,
        eventName: "Subscription Renewed",
        metadata: {
          subscription_id: data.subscription_id,
          product_id: data.product_id,
          customer_email: data.customer.email,
        },
      };
    } else {
      // Cancel dispatch if no click ID (organic traffic)
      webhook.cancel = true;
    }
  }

  return webhook;
}

跟踪不含税销售

仅将税前金额发送到 Dub 以进行准确的收入跟踪:
sale_without_tax.js
function handler(webhook) {
  if (webhook.eventType === "payment.succeeded") {
    const payment = webhook.payload.data;

    if (payment.metadata && payment.metadata.dub_click_id) {
      // Calculate pre-tax amount (total minus tax)
      const preTaxAmount = payment.total_amount - (payment.tax || 0);

      webhook.payload = {
        clickId: payment.metadata.dub_click_id,
        externalId: payment.metadata.dub_external_id || payment.customer.customer_id,
        amount: preTaxAmount, // Pre-tax amount in cents
        currency: payment.currency || "USD",
        paymentProcessor: "dodo",
        invoiceId: payment.payment_id,
        metadata: {
          total_amount: payment.total_amount,
          tax_amount: payment.tax || 0,
          customer_email: payment.customer.email,
        },
      };
    } else {
      // Cancel dispatch if no click ID (organic traffic)
      webhook.cancel = true;
    }
  }
  return webhook;
}

使用自定义事件名称跟踪销售

使用自定义事件名称对不同类型的销售进行分类:
custom_events.js
function handler(webhook) {
  if (webhook.eventType === "payment.succeeded") {
    const payment = webhook.payload.data;

    if (payment.metadata && payment.metadata.dub_click_id) {
      // Determine event name based on payment type
      let eventName = "Purchase";
      if (payment.subscription_id) {
        eventName = "Subscription Purchase";
      } else if (payment.metadata && payment.metadata.is_upgrade) {
        eventName = "Plan Upgrade";
      }

      webhook.payload = {
        clickId: payment.metadata.dub_click_id,
        externalId: payment.metadata.dub_external_id || payment.customer.customer_id,
        amount: payment.total_amount,
        currency: payment.currency || "USD",
        paymentProcessor: "dodo",
        invoiceId: payment.payment_id,
        eventName: eventName,
        metadata: {
          product_id: payment.product_cart ? payment.product_cart.map(product => product.product_id).join(', ') : undefined,
          customer_email: payment.customer.email,
        },
      };
    } else {
      // Cancel dispatch if no click ID (organic traffic)
      webhook.cancel = true;
    }
  }
  return webhook;
}

替代方案:客户端实现

如果您更喜欢从服务器跟踪销售而不是使用 webhook,您可以在成功支付后直接调用 Dub 的 Track API:
'use server';

import { Dub } from 'dub';

const dub = new Dub();

export async function trackSale(
  paymentId: string,
  clickId: string,
  customerId: string,
  amount: number,
  currency: string
) {
  await dub.track.sale({
    clickId: clickId,
    externalId: customerId,
    amount: amount,
    currency: currency,
    paymentProcessor: 'dodo',
    invoiceId: paymentId,
  });
}

最佳实践

尽早捕获点击 ID:在结账流程中尽早存储 Dub 点击 ID,以确保准确的归因,即使用户导航离开并稍后返回。
  • 始终在元数据中包含点击 ID:没有点击 ID,Dub 无法将收入归因于您的链接
  • 一致使用外部 ID:传递您在系统中使用的相同客户 ID,以便进行准确的客户级分析
  • 优雅处理自然流量:在没有点击 ID 时设置 webhook.cancel = true 以避免不必要的 API 调用
  • 使用示例支付进行测试:在上线之前验证集成是否正常工作
  • 监控您的 Dub 仪表板:检查销售是否正确显示并具有适当的归因

重要说明

  • 金额格式:Dub 期望金额以美分为单位(例如,$10.00 = 1000)
  • 货币:使用 ISO 4217 货币代码(USD、EUR、GBP 等)
  • 免费试用:$0 付款不被视为销售
  • 退款:如有需要,考虑单独跟踪退款以进行准确的收入报告

故障排除

  • 验证您的 Dub API 密钥是否正确,并具有 conversions.write 范围
  • 检查 dub_click_id 是否被捕获并存储在支付元数据中
  • 确保 webhook 转换正确格式化有效负载
  • 验证 webhook 是否在 payment.succeeded 事件上触发
  • 确认为您的 Dub 链接启用了转化跟踪
  • 确认用户在结账前是否点击了您的 Dub 短链接
  • 验证 dub_id cookie 是否在您的域上正确设置
  • 检查点击 ID 是否在结账创建和支付完成之间匹配
  • 确保您在创建结账会话之前捕获点击 ID
  • 验证 JSON 结构是否符合 Dub 的 Track Sale API 格式
  • 检查所有必需字段(clickId, externalId, amount)是否存在
  • 确保金额以美分为单位(整数,而不是小数)
  • 验证 API 端点 URL 是否正确:https://api.dub.co/track/sale
  • 使用示例 webhook 有效负载测试转换
  • 确保您仅在 payment.succeeded 事件上进行跟踪,而不是 payment.processing
  • 为每笔销售使用唯一的 invoiceId
  • 对于订阅,在续订时附加时间戳或计费周期以防止重复

其他资源

需要帮助吗?请联系 Dodo Payments 支持,邮箱:[email protected],以获取集成的帮助。