ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

使用 Dub 将 Stripe 自定义客户创建流程与点击归因关联:dubCustomerExternalId 与 dubClickId 元数据实战指南

2026/9/12 1:52:06 拓冰建站 浏览量
使用 Dub 将 Stripe 自定义客户创建流程与点击归因关联:dubCustomerExternalId 与 dubClickId 元数据实战指南 使用 Dub 将 Stripe 自定义客户创建流程与点击归因关联dubCustomerExternalId 与 dubClickId 元数据实战指南【免费下载链接】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导读本文面向使用 Stripe 但不走 Checkout Session 标准创建流程、而是自行调用stripe.customers.create/stripe.customers.update管理客户的开发者讲解如何在 Stripe 客户对象上写入 Dub 归因元数据dubCustomerExternalId、dubClickId让 Dub 在客户产生购买后自动把订单金额、币种等信息回填到用户最初点击的链接Click Event上从而实现完整的点击 → 线索 → 销售归因闭环。读完本文你将掌握 Dub Stripe 集成的客户侧接入方式、元数据字段的确切语义以及 Dub 服务端 Webhook 处理这些元数据时的完整解析与关联逻辑。一、为什么要在 Stripe 客户上写入 Dub 元数据Dub 的 Stripe 集成核心机制是通过 Webhook 监听 Stripe 侧的事件再把事件中的金额、币种等销售信息关联回 Dub 侧已经记录的点击事件。而关联的锚点就是 Stripe 客户对象上的metadata字段。在标准流程stripe-checkout.md中你只需在checkout.sessions.create时把用户 ID 写入metadata.dubCustomerExternalIdDub 就能在checkout.session.completed事件到达时完成归因。但如果你不使用 Checkout Session而是直接在服务端创建 Stripe 客户例如自有支付页面、自定义订阅管理、B2B 记账等场景就必须在客户创建/更新流程中主动传递归因信息——这正是本文所依托文档 stripe-customers.md 的适用场景。二、核心概念两个元数据字段在 Stripe 客户对象的metadata字段中需要写入两个 Dub 约定的键元数据键含义来源用途dubCustomerExternalId你数据库中该用户的唯一用户 ID你的业务数据库如user.id让 Dub 在本地Customer记录与 Stripe 客户之间建立一一对应关系dubClickId用户在落地页产生点击事件时下发的dub_idHTTP 请求头dub_id由 Dub 的点击跟踪脚本或服务端 SDK 下发让 Dub 把新客户关联回最初带来该访客的链接与点击事件需要特别说明的是只有dubCustomerExternalId是必须的dubClickId的作用是在客户首次出现时找到对应的点击事件从而把客户挂到某个链接/合作伙伴上。从 sync-customer.ts/api/stripe/integration/webhook/utils/sync-customer.ts#L82-L96) 的源码可以看到如果 metadata 中没有dubClickIdWebhook 会返回Click ID not found in Stripe customer metadata, skipping...并跳过新客户创建而对于已存在的 Dub 客户即使没有dubClickId也能正常更新。三、获取点击 IDdub_iddub_id是 Dub 在每次链接点击事件中生成的唯一标识通过请求头dub_id随用户请求一起下发到你的服务端。在 Node.js 服务中读取方式如下const dub_id req.headers.get(dub_id);它通常配合你现有的会话体系Cookie、JWT 等一起使用用户在落地页被 Dub 跟踪后后续到达你的支付/注册接口时请求头中就会携带该用户来源对应的dub_id。四、在 Stripe 客户创建流程中写入元数据当你的后端调用stripe.customers.create创建客户时将用户 ID 作为dubCustomerExternalId、请求头中的dub_id作为dubClickId一起写入metadataimport { stripe } from /lib/stripe; const user { id: user_123, email: userexample.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, }, });email、name可保持你原有逻辑不变dubCustomerExternalId建议使用数据库中的主键字符串如user_123保证全局唯一、稳定不变dubClickId直接透传请求头dub_id即可缺失时 Dub 端会跳过新客户创建详见下文源码解析。五、在 Stripe 客户更新流程中补充元数据如果你的应用是先创建 Stripe 客户、后补录归因信息例如用户在注册后一段时间才通过推广链接完成转化可以在stripe.customers.update中补齐同样的两个字段import { stripe } from /lib/stripe; const user { id: user_123, email: userexample.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, }, });由于 Stripe 的metadata是整体合并写入的更新时只需携带归因相关键即可不会覆盖你已有的其他业务元数据。六、源码级原理Dub 如何消费这些元数据6.1 Webhook 入口与事件类型Dub 的 Stripe 集成 Webhook 位于 apps/web/app/(ee)/api/stripe/integration/webhook/api/stripe/integration/webhook)其中sync-customer.ts专门处理customer.created与customer.updated两类事件。这两个事件正是触发客户关联的时机客户创建/更新时Dub 会读取客户对象上的metadata。6.2 外部 ID 的解析优先级在 get-dub-customer-external-id-from-metadata.ts/api/stripe/integration/webhook/utils/get-dub-customer-external-id-from-metadata.ts#L8-L21) 中Dub 从 metadata 解析外部 ID 的顺序是dubCustomerExternalId本指南推荐的主键字段dubCustomerId旧版/兼容字段user_idLemon Squeezy 客户迁移到 Stripe 后的兼容字段解析结果为空字符串或null时返回undefined随后sync-customer.ts会返回External ID not found in Stripe customer metadata, skipping...直接跳过。6.3 客户查找、更新与创建的完整流程sync-customer.ts的实际处理链路如下从事件对象中取出stripeCustomer.metadata解析dubCustomerExternalId与dubClickId用OR条件在 Dub 数据库中查找客户(projectId externalId)或stripeCustomerId若已存在直接更新该客户的stripeCustomerId、projectConnectIdStripe Account ID、externalId并在 Stripe 提供非空值时才覆盖name/email若不存在必须存在dubClickId才会继续——通过 Tinybird 查询点击事件getClickEvent校验该链接归属同一 workspace然后调用getOrCreateCustomer创建 Dub 客户cus_前缀 ID并继承点击事件中的linkId、programId、partnerId、clickId、clickedAt、country等字段新客户创建成功后记录一条名为New customer的 Lead 事件同时递增链接的leads计数、workspace 的usage并异步触发工作流leadRecorded、合作伙伴 Postbacklead.created、workspace Webhooklead.created与 Google Ads 转化上传。并发场景下若两个 Webhook 同时创建同一客户getOrCreateCustomer的findMode: first逻辑会保证只创建一个另一个走更新分支。6.4 客户创建/更新后的销售归因当客户在后续产生购买时Dub 通过另外两个处理器完成销售归因checkout.session.completedcheckout-session-completed.ts/api/stripe/integration/webhook/checkout-session-completed.ts)读取 Checkout Session 的 metadata 或已关联客户找到该客户的 Lead 事件后记录 Saleinvoice.paidinvoice-paid.ts/api/stripe/integration/webhook/invoice-paid.ts)先用stripeCustomerId反查 Dub 客户查不到时再回连 Stripe 获取客户 metadata 中的dubCustomerExternalId通过(projectConnectId, externalId)复合唯一键更新客户并补记stripeCustomerId最后记录订阅续费/发票支付类型的 Sale 事件。两个处理器都会在 Redis 中以trackSale:stripe:invoiceId:{invoiceId}为键做 7 天幂等去重避免同一张发票被重复计入销售额。这也解释了为什么文档强调当客户产生购买时Dub 会自动将购买详情发票金额、币种等关联到原始点击事件——正是这套 Webhook 链路在背后完成关联。七、集成后的数据流全景整个归因链路可以概括为用户点击 Dub 短链 → Dub 生成dub_id点击事件并记录到 Tinybird用户在你的站点完成注册/支付请求头携带dub_id你的服务端创建/更新 Stripe 客户把dubCustomerExternalId用户 ID与dubClickIddub_id写入metadataStripe 触发customer.created/customer.updatedWebhookDub 在本地创建或更新Customer并记录New customerLead 事件客户后续支付触发checkout.session.completed/invoice.paidDub 记录 Sale 事件金额、币种、发票号递增链接sales/saleAmount/conversions必要时为合作伙伴生成佣金并发送 Postback。八、最佳实践与注意事项用户 ID 必须稳定唯一dubCustomerExternalId一旦确定就不要变更它是 Dub 客户记录与 Stripe 客户长期绑定的主键若 ID 变化会导致历史归因断裂。保持幂等Webhook 对同一发票做了 Redis 去重你侧重复调用customers.update不会产生副作用可以放心重试。非 USD 币种自动换算销售金额会统一换算为 USD 记录convert-currency.ts不需要你自行处理币种转换。免费试用与折扣控制工作区可在 Stripe 集成设置中配置freeTrials是否将订阅免费试用记录为 Lead与discountCodeRestrictions仅首次交易可使用折扣码配置模式见 schema.ts。订阅场景的元数据优先级若同时使用了 Checkout Session 流程Dub 会优先读取 Checkout Session 的metadata其次才是已连接客户对象上的metadata两种方式互不冲突。通过本文的接入方式即使你的支付流程完全自定义、不经过 Stripe Checkout也能让 Dub 精确地把每一笔销售回溯到最初的点击来源为链接转化率分析、合作伙伴佣金结算和 Google Ads 转化回传提供准确的数据底座。【免费下载链接】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),仅供参考