首页
/ Cal.com(cal.diy)Dub 集成指南:OAuth 短链归因分析与预订转化追踪实战

Cal.com(cal.diy)Dub 集成指南:OAuth 短链归因分析与预订转化追踪实战

2026-09-08 19:49:57作者:舒璇辛Bertina

本文围绕 cal.diy(Cal.com 开源仓库)中 packages/app-store/dub 应用展开,系统讲解如何在日历预订平台中接入 Dub.co 短链归因服务:从「安装 → 客户端 SDK 配置 → 预订事件自动上报」三步启用链路,再到 OAuth 授权、令牌刷新与转化事件落库的源码级实现。读完本文,你将掌握 Dub 应用在 Cal.com App Store 中的完整工作方式,并能独立排查、扩展这一归因分析集成。

一、Dub 应用是什么:为预订平台引入短链归因分析

Dub 是「现代链接归因平台(modern link attribution platform)」,核心能力是创建短链、追踪转化分析(conversion analytics)并支持联盟(affiliate)计划。在其官方描述中,它提供 Clicks / Leads / Sales 三级指标追踪,让运营者能看清「哪条链接带来了哪次预订」。

在 cal.diy 仓库中,Dub 被实现为一个标准的 App Store 应用,其元信息定义在 config.json

  • name: Dubslug: dubtype: dub_analytics
  • variant: analyticscategories: ["analytics"]——它被归入**分析(analytics)**类应用,与日历、视频会议等类别区分;
  • 依赖包为 dub-package(即 npm:dub@^0.61.12),见 package.json

也就是说,用户可以在 Cal.com 的 App Store 中搜索并安装 Dub,将其凭证(OAuth Token)托管于平台,之后所有预订事件即可通过 Dub 的追踪接口上报,形成「短链 → 点击 → 线索 → 销售」的完整归因闭环。

二、三步启用:从登录到自动捕获(官方起步流程)

官方 DESCRIPTION.md 给出了非常精炼的启动步骤,这也是安装 Dub 应用后的标准操作路径:

  1. 登录 Dub.co 账号:在 Cal.com 中安装 Dub 应用,通过 OAuth 授权将你的 Dub 工作区与 Cal.com 账号绑定(授权细节见下一节)。
  2. 在你的网站上配置 Client SDK:按 Dub 官方文档在网站中接入客户端 SDK,并且必须将 app.cal.com 加入 Outbound Domains(出站域名)——这一步用于启用跨域追踪(cross-domain tracking),确保用户从你网站上的短链跳转到 Cal.com 预订页后,点击来源信息仍能通过 cookie / 查询参数正确传递。
  3. 预订事件发生后自动捕获:无需再手动上报,当 booking 事件产生时,Cal.com 的后台任务会自动把转化数据推送给 Dub。

三步流程的“自动化”部分正是这个应用的技术核心,下文将结合源码逐层展开。

Dub 归因分析仪表盘:展示 Clicks、Leads、Sales 核心指标与短链点击趋势

三、OAuth 授权流程:addcallback 两个端点的职责

Dub 应用采用标准的 OAuth 2.0 Authorization Code 流程,入口由 api/add.tsapi/callback.ts 两个 Next.js API 端点实现。

3.1 发起授权(add 端点)

add.ts 的职责是构造并跳转到 Dub 的授权页面 https://app.dub.co/oauth/authorize,关键参数如下:

  • client_id:来自 Cal.com 服务端存储的 Dub App Keys(通过 getParsedAppKeysFromSlug("dub", dubAppKeysSchema) 读取);
  • redirect_uri:即回调地址 redirect_uris,同样来自 App Keys;
  • response_type=code:声明使用授权码模式;
  • scopescopeString,定义在 lib/utils.ts,值为 "workspaces.read",即仅申请「读取工作区」的最小权限;
  • state:若当前处于团队上下文(teamId 为合法数字),会以 {"teamId": N} 的形式注入,用于授权返回后恢复上下文。

同时,add 端点要求用户已登录(否则返回 401),并通过 defaultHandler / defaultResponder 这套 Cal.com 统一 API 封装响应。

3.2 换取令牌并落库(callback 端点)

callback.ts 处理 Dub 授权后重定向回来的 code

  1. 校验 code 是否为字符串,并解析 OAuth state
  2. POST 请求 https://api.dub.co/oauth/token,携带 codeclient_idredirect_uriclient_secretgrant_type=authorization_code(表单编码);
  3. 若 Dub 返回非 200,读取响应中的 error.message 回传 400;同时解析 state.onErrorReturnTo / state.returnTo 做安全跳转(经 getSafeRedirectUrl 校验);
  4. 成功后,将响应中的 expires_in 秒数转换为绝对时间戳 expiry_date,再调用 createOAuthAppCredential({ appId: "dub", type: "dub" }, responseBody, req) 把令牌存入用户的 Credential 记录;
  5. 最后重定向到应用安装页(getInstalledAppPath({ variant: "analytics", slug: "dub" }))或 state.returnTo 指定页面。

令牌结构定义在 lib/type.tsDubOAuthToken 接口:access_tokenrefresh_tokentoken_type: "Bearer"expires_inexpiry_date(可选)与 scope

3.3 服务端 App Keys 校验

无论是 add 还是 callback,都要先经过 Zod 校验读取 App Keys。校验模式 dubAppKeysSchema 位于 lib/utils.ts,同时在 zod.tsappKeysSchema 中再次声明:

z.object({
  client_id: z.string(),
  client_secret: z.string(),
  redirect_uris: z.string(),
})

即:自托管部署者必须为 Dub 应用配置 DUB_CLIENT_IDDUB_CLIENT_SECRETDUB_REDIRECT_URIS 三类密钥(以 app.dub.co 中注册的 OAuth App 信息为准),缺少任一字段都会导致授权流程无法发起。

四、事件上报核心:AnalyticsServicesendEvent

Cal.com 定义了统一的分析服务抽象 packages/types/AnalyticsService.d.ts

export interface SendEventProps {
  name: string;
  email: string;
  id: string;
  eventName: string;
  externalId?: string;
}

export interface AnalyticsService {
  sendEvent(props: SendEventProps): Promise<void>;
}

Dub 的适配实现位于 lib/AnalyticsService.tsDubService 类,其 sendEvent 最终调用 Dub SDK 的追踪接口:

await this.dubClient.track.lead({
  clickId: id,              // 预订事件关联的短链点击 ID
  customerName: name,
  customerEmail: email,
  externalId: externalId ?? email,
  eventName: eventName ?? "Cal.diy lead",
});

值得注意的实现细节:

  • 工厂函数导出:文件底部通过 BuildAnalyticsService(credential) 工厂返回 AnalyticsService 实例,注释明确说明这是为了防止 Dub SDK 类型泄漏进生成的 .d.ts 文件,是良好的包边界设计;
  • 应用注册表analytics.services.generated.ts 中由 yarn app-store:build 自动生成 AnalyticsServiceMap,当前映射为 dub: import("./dub/lib/AnalyticsService")(E2E 模式下置空);
  • 异步任务投递:实际调用链并不在预订请求的同步路径中。packages/features/tasker/tasks/analytics/sendAnalyticsEvent.ts 通过 Tasker 异步消费事件:解析 { credentialId, info } 载荷 → 按凭据找到 AnalyticsManagermanager.sendEvent(info);失败时记录日志并故意 rethrow 以触发重试,保证归因数据不因瞬时故障丢失。

Dub 转化漏斗:从 Clicks(7.2K)到 Leads(165)再到 Sales,展示链接到预订的完整归因路径

五、令牌生命周期:过期检测与自动刷新

OAuth access token 有时效,DubService 在每次 initClient() 时处理:

  1. 读取 App Keys(client_idclient_secret),缺失则记日志并放弃上报;
  2. 从 Credential 中取出 DubOAuthToken
  3. 通过 isTokenExpired 判断:无 access_token 视为过期;存在 expiry_date 且小于当前时间则过期;
  4. 过期则调用 refreshAccessToken(refresh_token)——向 https://api.dub.co/oauth/token 发送 grant_type=refresh_token 换取新令牌;
  5. 刷新成功后更新 expiry_date 并写回 Credential;若 Dub 返回 401,则将该 Credential 标记为 invalid: true(见 lib/AnalyticsService.ts)。

这保证了长期运行的自托管实例在令牌轮换后无需人工干预即可继续上报转化事件。

六、常见问题排查要点

结合上述源码,可以给出如下排障路径:

  • 无法发起授权:优先检查 DUB_CLIENT_ID / DUB_CLIENT_SECRET / DUB_REDIRECT_URIS 三类 App Keys 是否已配置且与 Dub OAuth App 一致(见 zod.ts);
  • 授权后 400 报错:查看 Dub 返回的 error.message,通常是 scope 或 redirect_uri 不匹配;
  • 预订后无追踪数据:确认网站 Client SDK 已把 app.cal.com 加入 Outbound Domains(跨域追踪前提,见 DESCRIPTION.md),并检查 Tasker 任务日志中 sendAnalyticsEvent 是否重试(sendAnalyticsEvent.ts);
  • 凭据失效:若 Dub 侧撤销了授权,Credential 会被标记 invalid,需要在 Cal.com 中重新安装 Dub 应用走一遍 OAuth。

七、小结

Dub 应用是 Cal.com App Store「分析(analytics)」类集成的一个典型范例:对外只暴露「安装 → SDK 跨域配置 → 自动上报」三步用户流程,对内则完整落地了 OAuth 授权、最小 scope(workspaces.read)、令牌刷新、Credential 持久化与 Tasker 异步事件投递。无论你是自托管用户想启用转化归因,还是开发者希望参考 App Store 应用的工程模式,packages/app-store/dub 都是一个结构清晰、可直接对照实现的样例。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
858
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
923
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.83 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
524
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
393