@ 曲线技术/分析-节点
Curve 服务器边事件客户端 。 节点18+,零依赖.
运行 并行 您现有的浏览器跟踪器( Curve) /t.js或另一个供应商)。 没有
与它们一起声明: 这是从您的服务器中单独发送的 HTTPS 呼叫, 因此两者可以并行运行
无限.
兄弟姐妹包 : @curvetechnologies/analytics-react-native
iOS/Android应用程序.
问题解决
浏览器事件知道 用户来自何处 (单位:千美元) gclid编号 :fbclid 在登陆 URL 中),但无法看到
用户关闭标签后会发生什么: 由 Strepe webhook 确认的付款, 订阅
被晚间工作续约.
服务器事件是相反的:它们知道 发生了什么事 但没有点击ID.
曲线重接两个 电子邮件数字 :
- 您启动的页面会捕捉浏览器中的电子邮件, 点击的ID仍然存在 。 曲线写入
两者均改为
email_click_ids键入(organization_id, email_canonical)。 。 。 .email_canonical实值 简单lower(email),所以加地址在两侧都很重要. - 几天后,你的服务器张贴了 购买,除了那封邮件.
- 在摄入曲线上查看电子邮件,并重新附上原件
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'。文档,加上条纹标识警告 |
requiresEmail | true | 无法使用电子邮件时拒绝调用 |
expectsStripeCustomer | false | 当买到没有花旗客户身份证时发出警告( 仍在发送) |
双重计数警卫
如果您的登记人也宣布了事件,则您 应用 浏览器中起火,标记 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 没有网络的完全验证.