在 Supabase Auth 回调中接入 Dub Lead 转化跟踪:从dub_idCookie 归因到 Sign Up 事件上报
【免费下载链接】dubThe modern link attribution platform. Loved by world-class marketing teams like Framer, Perplexity, Superhuman, Twilio, Buffer and more.项目地址: https://gitcode.com/GitHub_Trending/du/dub
本文基于 Dub 开源仓库中的官方集成指南,完整讲解如何在基于 Supabase 的 Next.js 应用里,通过在/api/auth/callback回调路由中上报 Lead 转化事件,把"点击 Dub 短链 → 注册成为新用户"这一链路以可归因的方式接入 Dub 的分析与归因体系。读完本文,你将掌握dub.track.lead的完整调用方式、dub_idCookie 的来源与生命周期,以及如何用 10 分钟新用户窗口精确区分"新注册"与"老用户登录",避免重复上报与归因污染。
一、背景:Dub 的转化归因模型为什么依赖dub_id
Dub 的转化归因(Lead / Sale 归因)核心思路是:一次点击对应一个全局唯一的点击 ID(clickId),后续发生的转化事件必须携带该 ID 才能回溯到来源链接。
在 Dub 的短链跳转链路中,当用户点击一个启用了转化跟踪(trackConversion)的短链时,服务端会为该次点击生成一个 16 位随机 ID(nanoid(16)),并以 Cookie 形式种在浏览器里。从仓库源码 lib/middleware/link.ts 可以看到这一机制的实现细节:
- Cookie 的命名格式为
dub_id_{domain}_{key},例如访问dub.sh/launch后浏览器会持有名为dub_id_dub.sh_launch的 Cookie; - 只有当链接满足"启用转化跟踪 / 合作伙伴链接 / Singular、AppsFlyer 跟踪 URL"等条件时,点击 ID 才会被缓存(
shouldCacheClickId),从而支撑后续的/track/lead请求归因; - 若命中 Redis 中缓存的点击记录,会复用原 clickId,否则新生成一个。
而 Dub 的客户端分析脚本(gtm-client-sdk.md 中提到的dubcdn.com/analytics/script.js)与各类 SDK 在对外接口上统一将归因标识暴露为dub_idCookie。因此,在应用服务端读取dub_idCookie,就等价于拿到"用户是经由哪一次 Dub 点击进入本站"这一归因信息。
二、集成前提:准备好 Supabase 客户端与 Dub SDK
在编写回调路由之前,你的应用需要具备以下基础能力:
- Supabase 服务端客户端:用于在回调中通过
exchangeCodeForSession完成 OAuth Code 换取会话。指南中的示例引用了@/lib/supabase/server,即应用内对 Supabase SSR 客户端的封装,通常基于@supabase/ssr的createServerClient实现,并接收cookies()作为存储。 - Dub SDK 客户端:指南中的
import { dub } from "@/lib/dub"对应应用内对 Dub TypeScript SDK 的实例化。仓库自身就是这么做的,见 apps/web/lib/dub.ts:
import { Dub } from "dub"; export const dub = new Dub();new Dub()默认读取DUB_API_KEY环境变量作为鉴权凭据。因此请确保在部署环境中配置了DUB_API_KEY(可在 Dub 后台的 Tokens 页面生成),否则dub.track.lead会因缺少凭证而失败。
三、核心工作流:三步完成 Lead 事件上报
指南给出的整体思路可以浓缩为以下三步:
- 检查
dub_idCookie 是否存在:存在说明该用户确实是通过 Dub 短链点击进入的,具有归因价值; - 判断是否为"新注册用户":通过
user.created_at是否落在最近 10 分钟窗口内来判定,避免老用户每次登录都重复上报; - 上报 Lead 事件并清理 Cookie:调用
dub.track.lead把点击 ID 与用户身份绑定,随后删除dub_idCookie。
三步全部发生在 Supabase 的 Auth 回调中,因为这是服务端唯一能同时拿到"OAuth 用户信息 + 请求 Cookie"的位置。
四、完整实现:在/api/auth/callback中上报 Lead
以下为指南提供的完整代码,并补充了关键注释说明:
// app/api/auth/callback/route.ts import { dub } from "@/lib/dub"; import { createClient } from "@/lib/supabase/server"; import { waitUntil } from "@vercel/functions"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; 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`); }实现要点逐条拆解:
exchangeCodeForSession(code):Supabase Auth 的标准授权码交换流程。这里必须先于 Lead 上报完成,因为只有拿到data.user才能获得created_at、user_metadata等判断与上报所需的用户信息。dub_id的读取时机:务必在next/headers的cookies()上下文中读取,且 Cookie 名保持与 Dub 客户端脚本写入的一致。isNewUser的 10 分钟窗口:Date.now() - 10 * 60 * 1000是一个经验阈值,用于容忍注册流程中可能存在的网络延迟与重定向耗时;超过该窗口的用户会被视为老用户,不会重复上报。waitUntil包裹上报:来自@vercel/functions,它允许异步任务在响应返回后继续执行,从而不阻塞注册回调的重定向,把dub.track.lead变成"发后即忘"的后台任务。这与 Dub 的异步跟踪模式一脉相承(见下文mode: "async")。customerExternalId: user.id:以 Supabase 用户 ID 作为客户外部标识,此后该用户的所有后续事件(Sale 等)都会以这个 ID 为准进行归因聚合。- 删除 Cookie 的时机:在成功上报后立即删除,保证同一个浏览器后续登录时不会再触发重复上报。
五、dub.track.lead参数详解与可选字段
指南示例使用了 6 个参数,但 Dub 的 Lead 跟踪接口还支持更多可选字段。仓库的请求校验 Schema 定义在 apps/web/lib/zod/schemas/leads.ts,各参数说明如下:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
clickId | string | 是 | 点击的唯一 ID,即从dub_idCookie 读取的值,用于将 Lead 归因到具体点击 |
eventName | string | 是 | Lead 事件名称,最长 255 字符,同时可作为后续 Sale 事件关联的标识(通过/track/sale的leadEventName属性) |
customerExternalId | string | 是 | 你系统中客户的唯一 ID(此处为 Supabaseuser.id),最长 100 字符,将作为该客户所有后续事件的归因主键 |
customerName | string | 否 | 客户姓名,最长 100 字符;不传时 Dub 会生成随机名称(如 "Big Red Caribou") |
customerEmail | string | 否 | 客户邮箱,需符合 email 格式,最长 100 字符 |
customerAvatar | string | 否 | 客户头像 URL |
mode | enum(async/wait/deferred) | 否 | 默认async:不阻塞当前请求;wait:阻塞直到 Lead 完全落库;deferred:延后到后续请求再创建 |
eventQuantity | number | 否 | 事件数值(如免费试用开通的席位数量),设为 N 则该 Lead 会被记录 N 次,范围 1-100 |
metadata | object | 否 | 附加元数据,总长不超过 10,000 字符 |
两个值得留意的细节:
clickId也支持延迟归因:Schema 注释指出,如果传入空字符串,Dub 会尝试按customerExternalId查找已存在的客户并复用其clickId。这为"回调中拿不到 Cookie"的边缘场景提供了兜底方案。mode默认值是async:这意味着即使不手动包一层waitUntil,dub.track.lead本身也设计为不阻塞调用方;waitUntil的作用是让任务在响应返回后继续存活,两者配合效果最佳。
六、防重复上报的两种手段:新用户窗口 + Cookie 删除
这套方案的健壮性建立在两道保险之上:
- 时间窗口过滤:只对
created_at在最近 10 分钟内的用户上报。即便回调被重放、或用户刷新页面,只要不是"新注册",就不会再次上报。 - 一次性 Cookie:上报成功后立即
cookies().delete("dub_id")。Cookie 被消费后即失效,从根源上杜绝同一次点击产生多条 Lead 的可能。
仓库自身的认证链路采用了同样的"读 Cookie → 上报 → 删除"模式作为佐证。在 apps/web/lib/auth/track-dub-lead.ts 中,Dub 的 NextAuth 集成同样先读取dub_id,调用dub.track.lead(事件名同样为"Sign Up"),然后删除dub_id与dub_partner_data两个 Cookie。这说明"上报即删除"是 Dub 官方集成的标准做法,且合作伙伴归因数据(dub_partner_data)也会在同一步骤被清理,避免敏感归因信息长期驻留浏览器。
七、方案对比:Supabase 与 NextAuth / Auth0 / Clerk 集成方式的异同
本指南属于 Dub 认证集成系列中的一篇,同一套"dub_idCookie 归因"心智模型在其他认证方案中均有对应实现,可互相参照:
- NextAuth:在
signIn事件回调中通过message.isNewUser判断新用户,逻辑与本文的isNewUser等价,但判断责任由 NextAuth 承担; - Auth0:在
afterCallback中通过"数据库中是否已存在该邮箱用户"来判断新旧,同样读取dub_id、上报、删 Cookie; - Clerk:走客户端 Server Action / API 路由路线,用
user.publicMetadata.dubClickId标记"该用户是否已上报",实现幂等。
可以看到,不同认证体系只是"新用户判定"与"上报触发点"不同,核心的dub.track.lead({ clickId, eventName, customerExternalId, ... })调用与dub_idCookie 读取逻辑完全一致。这意味着你可以在多认证方案共存的架构中复用同一套归因上报封装。
八、验证与排错建议
完成接入后,建议按以下顺序验证链路是否打通:
- 确认 Cookie 已种下:先访问一条启用了转化跟踪的 Dub 短链,在浏览器 DevTools 的 Application → Cookies 中确认
dub_id(或dub_id_{domain}_{key})已写入; - 触发一次真实注册:带着该 Cookie 完成 Supabase Auth 注册流程,观察回调路由是否命中
dub_id && isNewUser分支; - 检查请求与后台:在 Network 面板确认存在发往 Dub 跟踪端点的请求,并在 Dub 控制台的 Lead 事件分析中看到对应记录;
- 验证幂等性:注册完成后再次登录同一账号,确认没有产生第二条 Lead 记录——这同时验证了 10 分钟窗口与 Cookie 删除两道保险。
若事件未上报,优先排查:DUB_API_KEY是否配置、dub_idCookie 是否在请求作用域内可读、user.created_at是否真的落在 10 分钟窗口内,以及@vercel/functions的waitUntil是否被正确引入。
相关文档
- Dub 官方指南:NextAuth 集成
- Dub 官方指南:Auth0 集成
- Dub 官方指南:Clerk 集成
- Dub 官方指南:GTM 埋点跟踪
- Dub 官方指南:手动调用 SDK / REST API 跟踪
- 仓库内 Dub SDK 客户端实例
- 仓库内 Lead 请求参数 Schema(含全部字段约束)
- 仓库内短链中间件对
dub_id_{domain}_{key}Cookie 的写入逻辑
【免费下载链接】dubThe modern link attribution platform. Loved by world-class marketing teams like Framer, Perplexity, Superhuman, Twilio, Buffer and more.项目地址: https://gitcode.com/GitHub_Trending/du/dub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考