@curvetechnologies/analytics-node

中文文档
0.1.1发布于 2026/08/19MITTypeScriptCommonJS零依赖近 30 天下载 2017

Curve 服务器边事件客户端 。 将后端转换和已存在的浏览器跟踪器一起发送到 Curve .

中文使用文档
文档目录19 节
  1. 问题解决
  2. 安装
  3. 设置
  4. domain 不是可选的,必须登记
  5. 在没有浏览器的情况下, 何谓识别对象
  6. 发送事件
  7. result.clickIdAttribution : 此转换是否真的加入 ?
  8. 三把钥匙
  9. 同意
  10. 同上:在接线前读取
  11. 时间戳和回填
  12. 失败行为
  13. 利率限制, 以及它们为什么从服务器上更重要
  14. 无服务器
  15. 事件登记
  16. 双重计数警卫
  17. 描述性名称,而非别名
  18. custom 属性
  19. 测试您的整合

@ 曲线技术/分析-节点

Curve 服务器边事件客户端 。 节点18+,零依赖.

运行 并行 您现有的浏览器跟踪器( Curve) /t.js或另一个供应商)。 没有 与它们一起声明: 这是从您的服务器中单独发送的 HTTPS 呼叫, 因此两者可以并行运行 无限.

兄弟姐妹包 : @curvetechnologies/analytics-react-native iOS/Android应用程序.


问题解决

浏览器事件知道 用户来自何处 (单位:千美元) gclid编号 :fbclid 在登陆 URL 中),但无法看到 用户关闭标签后会发生什么: 由 Strepe webhook 确认的付款, 订阅 被晚间工作续约.

服务器事件是相反的:它们知道 发生了什么事 但没有点击ID.

曲线重接两个 电子邮件数字 :

  1. 您启动的页面会捕捉浏览器中的电子邮件, 点击的ID仍然存在 。 曲线写入 两者均改为 email_click_ids键入 (organization_id, email_canonical)。 。 。 . email_canonical 实值 简单 lower(email),所以加地址在两侧都很重要.
  2. 几天后,你的服务器张贴了 购买,除了那封邮件.
  3. 在摄入曲线上查看电子邮件,并重新附上原件 gclid,因此谷歌广告属性 购买到导致它的点击.

后果: 电子邮件是 SDK 无法错误的一个字段 . 它的规范化与 弯道摄入 " <[email protected]> " 从服务器 webhook 和 [email protected] 从浏览器解析到同行。 需要电子邮件的事件 没有可用的是 拒绝客户端 而不是发送。 吃过但从未吃过的东西 属性比永不摄入的属性还要差,因为仪表板上看起来很好.


安装

npm install @curvetechnologies/analytics-node

客户在仪表板下到达SDK 配置 > 服务器 SDK,它自己的导航项目.

设置

// lib/curve.js. One module-level client, reused across requests.
const { CurveClient } = require('@curvetechnologies/analytics-node');

// Your allowlist: the descriptive event names you mapped in the Curve dashboard.
const CURVE_EVENTS = [
  { name: 'purchase_completed', stage: 'purchase', requiresEmail: true, expectsStripeCustomer: true },
  { name: 'lead_submitted',     stage: 'identity', requiresEmail: true },
];

module.exports.curve = new CurveClient({
  apiKey: process.env.CURVE_API_KEY,          // server-side only. Never NEXT_PUBLIC_*.
  events: CURVE_EVENTS,
  domain: 'example.com',                      // must be REGISTERED in the Curve dashboard, see below
  enabled: process.env.NODE_ENV === 'production',   // preview/CI validate but do not send
  onError: (err, ctx) => console.warn('[curve]', ctx.eventName, err.message),
});

apiKey, (中文(简体) ), events 财务报告和审定财务报表 domain 需要并投掷建筑。 这是有意的: 部署时的错误,在启动时应该失败,而不是默不作声地放弃每个转换一周.

endpoint 默认为 Curve 的公开摄入主机,因此大多数集成从未设置过. 如果 您的组织在自定义的摄入域.

domain 不是可选的,必须登记

POST /events/track 答题 400个缺少所需栏目 对于一个没有 domain,以及 403 域数 未登记 当价值不在组织的范围之内时 domains 页:1 注册主机名 曲线仪表板 在此之前 第一个部署,并通过赤色主机名(example.com没有 https://example.com/checkout,尽管一个 URL 已被接受并简化为您的主机名.

在没有浏览器的情况下, 何谓识别对象

该路线还要求 anonymous_user_id,因为没有设备。 这个 SDK 发送 email_<normalized email>,用于没有设备而到达的转换的合成键. Curve 将电子邮件 前缀视为不是设备,也不会发明来自它的访客. 过 anonymousUserId 明确,如果您有更好的稳定标识符.

发送事件

const result = await curve.track('purchase_completed', {
  email: session.customer_email,        // the join key
  stripeCustomerId: session.customer,   // 'cus_...', exists only after purchase
  purchaseOptionType: 'membership_annual',
  paymentIntentId: session.payment_intent,
  value: session.amount_total / 100,    // MAJOR units. Stripe reports cents; /100 or report 100x revenue
  currency: session.currency,
});

if (!result.ok) {
  // Never throws. Log and carry on. Your checkout must not fail because Curve is unreachable.
  console.warn('[curve]', result.reason, result.message);
}

// result.warnings is worth logging too: dropped currency codes, a purchase missing its Stripe ID, and
// reserved `custom` keys are reported there rather than by failing the send.

result.clickIdAttribution: 此转换是否真的加入 ?

ok: true 表示曲线接受事件. 没错 没有 表示事件会属性化. 一个买家 在浏览器的摄入量中 从来没有看到过电子邮件 和加入的一样幸福 完全一样,然后到达Google/Meta 没有任何信用。 这就是整个失败 整合的存在是为了预防,它从状态代码中看不见.

因此,路线报告一个判决 和SDK携带它通过:

result.clickIdAttribution含义
'present'你自己发了个身份证明 没什么好恢复的.
'recovered'曲线重接了浏览器会话的点击ID出局 email_click_ids。 。 。 . 这是幸福的道路.
'none'没有什么可归结的 该事件被存储并转发,但无属性.
缺席后端没有报告:旧部署,或非转换事件.
if (result.clickIdAttribution === 'none') {
  metrics.increment('curve.unattributed_purchase');   // alert if this stops being rare
}

稳住的一滴 'none' 这是正常的(直接交通、有机的、从未见过广告的访客)。 A 类 步骤 变动 指浏览器方面停止了收取电子邮件,或双方停止同意. 这个 后端在 WARN 中也会用一个蒙面的电子邮件记录每个, 因此在 Sentry 中可以计算, 没有任何 工具在你身边.

三把钥匙

密钥当它存在时作用
email从开始的页面开始归属加盟,从浏览器会话中恢复 ID
purchaseOptionType在选择计划时按购入的部件转换
stripeCustomerId仅在购买后转变后的持久身份

仅在电子邮件上预购事件密钥 。 后购事所取所取所取三. SDK警告( 仍然) 当购买阶段的事件没有花旗标识时, 因为提前退账事件是正当的.

可选的, 并省略它不会改变任何东西: 未经同意的事件对象决定 Curve 的 允许/ BAA- 默认 Egress 姿态, 即服务器集成之前的每个行为 选项已存在.

等你的后端知道访客同意状态后发去 弯道的后卫也一样 适用于浏览器事件的规则: advertising_allowed: false切换到 联系字段的有限处理模式已剥去, Google Ads、 Microsoft 和 LinkedIn 则不 完全传送。 没有它,同一人的浏览器购买可以受到限制,同时 服务器侧的购买记录是无限制的.

await curve.track('purchase_completed', {
  email: session.customer_email,
  paymentIntentId: session.payment_intent,
  consent: {
    consent_given: true,
    advertising_allowed: false,   // this visitor opted out of advertising
    analytics_allowed: true,
    is_explicit: true,            // they chose, rather than inheriting a jurisdiction default
  },
})

只有布尔人。 非布尔( N)'false', (中文(简体) ), 0)作为 缺席从不被迫否认, 并警告人们不要沉默地重新解释. consent 内部 custom 是一个 保留了钥匙并被丢掉 所以有一条办法可以表示同意.

同上:在接线前读取

花旗传送网路 至少一次,所以一次付款可以到达两三次.

SDK得到一个决定论 UUIDv5 设备 event_id 从它发现的第一个稳定的商业键 (单位:千美元)orderId → paymentIntentId → checkoutSessionId → subscriptionId) (中文(简体) ). 每次重送都一样 因此,付款持有同样的身份证.

await curve.track('purchase_completed', {
  email, stripeCustomerId,
  paymentIntentId: event.data.object.payment_intent,   // <- makes the ID stable across redeliveries
});

确切地说,那买什么, 因为这不是所有的东西:

醉了吗?
Meta CAPI 转换, 使用Meta dedups (event_name, event_id)对两次交货成为一次转换
曲线 events 表格和曲线仪表板计数没有摄入没有将军 event_id 脱下

所以,决定性身份 使得 广告平台 对 它没有使 Curve 的生事件计数正确. 如果仪表板的号码必须准确,请将自己的已处理的网页表按住Frede的键 event.id 跳过 track() 请重新发送。 (身份证必须是UUID-形状,这样任何一种文件都可以使用) 工作:摄入路线保持客户端提供 event_id 仅当它与 v1- v5 UUID 相匹配且默默 删除其他任何内容。 )

如果没有稳定的密钥存在, ID是随机的, 这是正确的 "现在发生"事件 错误的 任何网友都能重现的. 在由webhook驱动的事件上总是通过一个稳定的密钥.

时间戳和回填

occurredAt 进入 毫秒 当你通过一个数字。 每条花纹时间戳session.created, (中文(简体) ), invoice.created, (中文(简体) ), event.created属于 秒数这样 occurredAt: session.created 日期为 改为1970年1月。 SDK 拒绝下方的任何时间值 1e12,警告,并使用电流 时间代替。 传出 a Date,或乘以1000.

occurredAt 套装 event_time_ms 在电线上,Meta把它作为转换时间 只有当它是 在最后一小时之内总是给事件自己盖章 occurred_at 作为 现在复出 因此,这周的网络呼号 与今天的事件 上周的 event_time_ms 记录 event_data它确实 没有 恢复原来的时间戳。 回填前与曲线交谈; Meta 也 硬弹道转换已超过七日.

失败行为

情况发生什么事
网络下行/ 超时( 3s 默认)然后用紧张的后退来重试 {ok:false, reason:'unreachable'}
5xx 或者说 429重审
401 编号 : 403 / 其他 4xx没有 重审, 因为一个错误的密钥或未注册的域名无法再次尝试修复
缺少/无效的电子邮件在提出任何请求之前被驳回missing_email(中文(简体) )
没有电子邮件也没有 anonymousUserId被拒绝(missing_identity),因为路线会回答400
待命 domain 这不是主机名被拒绝(invalid_domain(中文(简体) )
未知事件名称拒绝,因为登记处是许可名单(unknown_event(中文(简体) )
服务器发送的应用事件被拒绝(wrong_origin),参见双数后卫
 onError 处理器投掷吞没

track() 从不拒绝. 每一次失败都是一个解决的结果对象. 唯一的掷出在 建筑师.

默认值( E)timeoutMs: 3000, (中文(简体) ), retries: 2完全无法到达的曲线给呼叫者造成高达~9s的费用 加上紧张。 下调 retries 或用途 trackAsync() 如果它包含在用户面前的请求中.

利率限制, 以及它们为什么从服务器上更重要

允许无效 每实施伙伴每分钟1 000个事件 财务报告和审定财务报表 每个组织每分钟10 000美元。浏览器 流量遍及每个访客的IP,从未接近每个IP的限制;您的后端帖子 所有来自一两个egress地址的东西, 所以它是第一个可以到达它的集成.

网路交通稳定 离这里不远 批量回填或夜间工作 即: 超过限制的路由答案 429而SDK的两台回放 共用同样的爆裂而不是 把它冲干 Pace批量发送到每分钟几百个,并在大回填前与Curve交谈.

无服务器

在韦尔塞尔/兰布达, await track() 或使用平台的 waitUntil()。 。 。 . trackAsync() 立即返回, 但是,一个没有服务器的容器可以在返回回复的瞬间被冻结,从而杀死飞行中的发送.

事件登记

您通过的阵列 events 是许可列表. track() 拒绝任何没有声明的名字, 所以打字 在测试中失败, 而不是悄悄创建垃圾事件类型 。 每个条目:

外地默认含义
name所需经费描述事件名称, 与仪表板上的地图完全相同
origin'server''in_app' 标记您的应用程序在浏览器中起火的事件; track() 拒绝
stage'lifecycle''identity' 编号 : 'purchase' 编号 : 'lifecycle'。文档,加上条纹标识警告
requiresEmailtrue无法使用电子邮件时拒绝调用
expectsStripeCustomerfalse当买到没有花旗客户身份证时发出警告( 仍在发送)

双重计数警卫

如果您的登记人也宣布了事件,则您 应用 浏览器中起火,标记 origin: 'in_app' 财务报告和审定财务报表 track() 将拒绝他们 reason: 'wrong_origin'。 。 。 .

这不是私奔。 当浏览器事件和服务器事件都描述同一购买时,发送 后端的每一次采购都算两次 报告ROAS的真相是两倍 那种错误 这看起来像一个好消息 几个星期。 最安全生产登记处只宣布服务器事件.

描述性名称,而非别名

发送 purchase_completed曲线绘制到你们组织的HIPAA安全 event_N 别名服务器侧。 这样做 未发送 event_N 从你的应用, 因为那绕过 映射你的遵守状态取决于。 一个 未标记的描述性名称作为绘图建议出现在仪表板上并被固定在那里.

custom 属性

任何内容 custom 合并为 event_data. SDK 本身的钥匙(email,条纹/顺序 识别器, value, (中文(简体) ), currency)并按住摄取路线储备(event_id, (中文(简体) ), event_time_ms, (中文(简体) ), user_id,移动摄入和同意字段) 在警告下抛出 而不是允许 覆盖他们。 一个生的 custom.email 降落在一个正常的上面 正是如何 归属关系会平静下来 点击 ID 是 没有 保留: 如果您的服务器持有一个真实的 gclid发送它.

PHI sanitization 仍然运行服务器侧,但不要依赖它. 保持临床数据 custom。 。 。 .

测试您的整合

const client = new CurveClient({
  apiKey: 'x', events: CURVE_EVENTS, domain: 'example.com', fetch: fakeFetch,
});
const built = client.buildPayload('purchase_completed', { email: '[email protected]', stripeCustomerId: 'cus_1' });
// inspect built.payload without sending anything

或者设定 enabled: false 没有网络的完全验证.

版本与发布记录

@curvetechnologies/analytics-node 共发布 2 个版本,首次发布于 2026/08/19;过去 12 个月发布了 2 个版本。

版本发布日期距今
0.1.1latest2026/08/192 个月前
0.1.02026/08/192 个月前

关于 @curvetechnologies/analytics-node 的常见问题

@curvetechnologies/analytics-node 是什么?

Curve 服务器边事件客户端 。 将后端转换和已存在的浏览器跟踪器一起发送到 Curve .

如何安装 @curvetechnologies/analytics-node?

在项目目录运行 npm install @curvetechnologies/analytics-node,或使用 pnpm add / yarn add / bun add。

@curvetechnologies/analytics-node 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@curvetechnologies/analytics-node 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@curvetechnologies/analytics-node 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@curvetechnologies/analytics-node 可以商用吗?

@curvetechnologies/analytics-node 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@curvetechnologies/analytics-node 的下载量有多少?

根据 npm 官方下载统计,@curvetechnologies/analytics-node 在 2026/10/11 – 2026/11/09 这 30 天内被下载 2,017 次,日均约 67 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@curvetechnologies/analytics-node 还在维护吗?

最新版本 0.1.1 发布于 2026/08/19(2 个月前);至今共发布 2 个版本,其中过去 12 个月发布了 2 个;首次发布于 2026/08/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。