API Reference
API Reference

Subotiz A/B 实验 SDK

Subotiz A/B 实验前端 SDK。在浏览器中拉取实验分组数据、支持按需上报转化事件。

  • 浏览器优先,不依赖任何 UI 框架(React / Vue / 原生页面均可)
  • 原生 TypeScript 支持,类型完整

通过 CDN 加载

可通过 CDN 引入 SDK。

CDN 模式调用语法:abSubotiz(方法名, ...参数)

<!-- 1. 同步定义全局代理与方法队列 -->
<script>
  window.abSubotizQueue = window.abSubotizQueue || [];
  window.abSubotiz = window.abSubotiz || function () {
    window.abSubotizQueue.push([].slice.call(arguments));
  };

  // 2. 立即开始排队调用
  abSubotiz('init', {
    storeId: 'your_store_id',
  });
  abSubotiz('onReady', function (experiments) {
    var hero = experiments['homepage_hero_test'];
    if (hero && hero.variantKey === 'variant_b') {
      document.documentElement.dataset.heroVariant = 'b';
    }
  });
</script>

<!-- 3. 异步加载真实 SDK,加载完成后会自动回放队列 -->
<script async src="https://cdn.subotiz.com/ab-sdk-subotiz/v1/ab-sdk-subotiz.min.js"></script>

上报事件

业务事件通过 track(eventName, properties?) 上报,常用于实验转化分析(注册完成、按钮点击、自有收入 / LTV 等平台无法自动采集的指标):

abSubotiz.track('signup_complete');                                  // 计数 / 去重类指标
abSubotiz.track('add_to_cart', { source: 'cta' });                   // 额外标签随事件透传
abSubotiz.track('ltv', { value: 99.9 });                             // 数值类指标(求和 / 均值)
abSubotiz.track('checkout_success', { value: 99, currency: 'USD' }); // value + 额外标签

特性:

  • 永远上报、永不丢弃——SDK 不在客户端做白名单拦截,任意 eventName 都会发往后端。是否计入某个实验由后端归因,无需前端关心
  • 永远成功返回,不抛出异常
  • 网络失败 / 5xx → 入 localStorage 离线队列,回到在线状态时自动重发
  • 4xx → 静默丢弃(事件本身不合法)
  • init 未完成时调用 → 静默跳过

不要在事件参数中传用户隐私数据(手机号、邮箱、身份证、地址等)。SDK 不做过滤,由调用方自行约束。

数值指标:properties.value

需要求和 / 平均值类聚合的指标(如收入、LTV、停留时长),把数值放在 properties.value 上:

abSubotiz.track('ltv', { value: 99.9, tier: 'pro' });
  • value 必须是有限数字typeof === 'number' 且非 NaN / Infinity)才会被后端纳入数值聚合。
  • 非法的 value(字符串 '99.9'NaNnull 等)不会污染数值统计,会作为普通标签原样透传,由后端按"非数值"降级处理。
  • 计数 / 去重类指标(如 signup_complete无需value,直接 track('signup_complete') 即可。
  • properties 中除 value 外的其他字段都作为附加标签随事件上报,字段名自定。

调试:指标白名单提示

后台为实验声明的指标 key 会随分桶结果下发,SDK 在 debug: true 时据此做拼写提示:当 trackeventName 不在任何运行中实验声明的指标里,控制台会 warn(提示可能拼写错误 / 大小写不符,并打印当前白名单)。

只是调试提示——事件依然正常上报。生产模式(debug: false)下完全无此开销,不影响上报行为。

曝光事件

实验首次命中时 SDK 会自动上报曝光事件,不需要手动调用。粘性缓存中已存在的实验不重复上报。


Pricing 链接自动注入

实验对象类型为 price_list 时,SDK 会在返回的价格表链接中,追加以下 query 参数。业务方拿到的链接已经是注入完毕的:

参数
ab_sbt_sid当前商家 id
ab_sbt_uid当前用户 id
ab_sbt_utype'user''anonymous'
ab_sbt_sch来源渠道(仅当 init 传入了合法 sourceChannel 时才追加,见下文)
 // → https://checkout.subotiz.com/pricing-v2?ab_sbt_sid=store_123&ab_sbt_uid=user_456&ab_sbt_utype=user
const exp = experiments['pricing_redirect_test'];
if (exp && exp.objectType === 'price_list') {
  window.location.href = exp.config!.link as string;
}

来源渠道定向 (sourceChannel)

init 时可传入 sourceChannel,用于让后端按流量来源做实验定向分流(例如"仅对来自 facebook 的流量开启某实验")。其余定向维度(设备、语言、国家、IP)由后端从 HTTP 请求头自动解析,SDK 不采集任何浏览器指纹 / UA / 地理位置信息

abSubotiz.init({
  storeId: 'your_store_id',
  sourceChannel: 'facebook',
});

传入后,SDK 会在两处携带该值:

  1. 分桶请求:写入 POST /sdk/v1/ab/assign 请求体的 attributes.source_channel,供后端定向规则匹配。
  2. Pricing 链接:对 price_list 类型实验的链接追加 ab_sbt_sch query 参数(与上面的值同源)。

取值与归一化规则

SDK 不做枚举校验、不做大小写转换,取值口径(如 facebook / google / tiktok)由你与产品侧自行约定。携带前仅做如下处理:

传入值处理结果
正常字符串trim 后携带(保留原始大小写)
undefined / null / 非字符串视为未传,不携带 source_channel 字段(不是空串占位)
空字符串 / 纯空白视为未传,不携带
trim 后长度 ≥ 50视为未传,不携带(debug: true 时控制台 warn 提示超长)

稳定性sourceChannel 在单次会话内被视为稳定值,仅 init 时确定一次。后续不监听变化、不会因渠道变化重新拉取分桶。

未携带优于截断:超长 / 非法值一律不携带,避免后端拿到被静默改写的值后与你的埋点配置产生偏差。


API 参考

init(config)

初始化 SDK。在每个 SDK 实例上只能调用一次,重复调用会被忽略。立即返回,不阻塞主线程。

  • 参数 config: SubotizSDKConfig
  • 返回 void

配置项

init(config) 接受一个 SubotizSDKConfig 对象:

字段类型默认说明
storeIdstring必填。商家 ID(Subotiz 后台店铺 access no)
sourceChannelstring可选。来源渠道,用于后端流量定向。详见 来源渠道定向
timeoutnumber30000分桶请求超时毫秒数
debugbooleanfalse打开调试日志,并允许 forceVariant 生效。生产环境保持关闭

onReady(onSuccess, onError?)

注册就绪回调。SDK 已就绪时同步触发,未就绪时异步触发。可重复注册多个。

abSubotiz.onReady(
  (experiments) => { /* 实验数据已就绪 */ },
  (error) => { /* 网络异常且无缓存 */ },
);
  • 参数
    • onSuccess: (experiments: SubotizExperimentMap) => void
    • onError?: (error: ABTestError) => void
  • 返回 void

waitReady()

onReady 的 Promise 版本。

const experiments = await abSubotiz.waitReady();
  • 返回 Promise<SubotizExperimentMap>
  • 失败时:抛出 ABTestError(与 onReadyonError 等价)

isReady()

同步检查 SDK 是否已就绪。

  • 返回 boolean

getExperiments()

同步获取当前所有命中实验。

  • 返回 SubotizExperimentMap | null
    • null —— 未初始化、未就绪、或当前没有任何命中实验
    • Record<string, SubotizExperiment> —— 实验映射表,键为 experimentId

track(eventName, properties?)

上报自定义业务事件。任意事件都会上报,是否计入实验由后端归因。

abSubotiz.track('signup_complete');                  // 计数 / 去重
abSubotiz.track('ltv', { value: 99.9, tier: 'pro' }); // 求和 / 均值,value 必须是有限数字
  • 参数
    • eventName: string —— 事件名
    • properties?: Record<string, unknown> —— 事件附加属性。其中 value(有限数字)用于求和 / 均值类指标;其余字段作为附加标签透传
  • 返回 void(永不抛错)
  • 行为
    • 永远上报,不在客户端做白名单丢弃
    • debug: trueeventName 不在后台声明的指标白名单中 → 控制台 warn 拼写提示(仅提示,不影响上报)
    • init 未完成时调用 → 静默跳过

forceVariant(experimentId, variantKey)

debug: true 时生效。本地强制覆盖某实验的变体,便于开发期验证不同变体的 UI。

abSubotiz.forceVariant('homepage_hero_test', 'variant_b');
  • 参数 experimentId: stringvariantKey: string
  • 返回 void

错误处理

onReady(_, onError)waitReady() 失败时返回 ABTestError

interface ABTestError {
  code: ABTestErrorCode;
  message: string;
  cause?: unknown;
}

错误码:

错误码含义
NETWORK_TIMEOUT请求超时(弱网)
NETWORK_ERROR网络请求失败(离线、跨域被拦截)
HTTP_ERROR后端返回非 2xx
INVALID_RESPONSE响应格式异常
NOT_INITIALIZEDinit 校验失败(如缺少 storeId
abSubotiz.onReady(
  (experiments) => render(experiments),
  (error) => {
    if (error.code === "NOT_INITIALIZED") {
      console.error('init 配置非法', error.message);
    }
   // do something
  },
);

降级策略

SDK 内置降级策略,业务方只需关心三件事:

  1. 首次渲染走对照组 —— await waitReady() 在网络异常时最长会等 timeout 秒,不要用它阻塞首屏
  2. onReady / waitReady 中切换变体 —— 用户视觉冲击最小
  3. onError 时保持对照组 —— 不展示空白或异常 UI
场景SDK 行为
二次访问 + 网络异常自动用 localStorage 缓存进入就绪态
首次访问 + 网络异常触发 onError
track 时 5xx / 离线入离线队列,window.online 时自动重发
track 时 4xx静默丢弃


调试

打开 debug: true 后,SDK 会在控制台输出请求与队列状态:

abSubotiz.init({ storeId: 'store_123', debug: true });
日志含义
[ab-sdk] reportExposure: sending N event(s)曝光上报中
[ab-sdk] track: sending N event(s)自定义事件上报中
[ab-sdk] track event queued (offline)事件入离线队列
[ab-sdk] 4xx error, track event dropped4xx 静默丢弃
[ab-sdk] MetricKeyRegistry rebuilt: N keys分桶成功后重建指标白名单(含 key 预览)
[ab-sdk] track('xxx') did not match the metric_keys ...track 事件名未命中白名单的拼写提示(仍会上报)
[ab-sdk-subotiz] sourceChannel length N >= 50, omitted ...sourceChannel 超长被忽略
[ab-sdk] Forced variant: xxx → yyyforceVariant 调用

重置 SDK 状态

Object.keys(localStorage)
  .filter((k) => k.startsWith('_ab_sdk_') || k.startsWith('ab_event_queue_'))
  .forEach((k) => localStorage.removeItem(k));

本地存储键

SDK 在 localStorage 中使用以下键:

键名内容
_ab_sdk_uuid匿名用户 UUID(跨租户共享)
_ab_sdk_exp_{storeId}_USERID实验分配快照(有效期 60 天)
ab_event_queue_{storeId}_USERID_track离线事件上报队列
ab_event_queue_{storeId}_USERID_exposure离线曝光事件队列

常见问题

SDK 加载之前业务代码已经渲染了,会拿不到实验数据?

会。让业务代码先渲染对照组,再在 onReady / waitReady 中切换到变体。粘性缓存保证用户刷新后稳定看到自己的变体(首次访问会有"对照组 → 变体"的瞬时切换)。

track 调用了,后端没收到怎么排查?

按顺序检查:

  1. abSubotiz.isReady() 是否为 true?未就绪时 track 静默跳过。
  2. 打开 debug: true,看控制台是否有 track: sending 日志。
  3. 看浏览器网络面板有没有 POST /sdk/v1/ab/events 请求。
  4. 响应是 4xx 还是 5xx?4xx 会被丢弃,5xx 会入队等待重发。
  5. localStorage.ab_event_queue_*_track 是否积压事件。

我传了 sourceChannel,但实验定向没生效

按顺序检查:

  1. 打开 debug: true,看分桶请求体 attributes.source_channel 是否带上了你的值。
  2. 确认值合法:trim 后非空、长度 < 50。超长会被忽略(控制台有 warn)。
  3. 确认值与后台定向规则完全一致——SDK 不做大小写转换,Facebookfacebook
  4. 定向规则匹配在后端完成;命中与否可用 getExperiments() 看是否拿到该实验。

trackvalue 没有进入求和 / 均值统计

value 必须是有限数字typeof === 'number' 且非 NaN / Infinity)才会被纳入数值聚合。常见坑是传了字符串:track('ltv', { value: '99.9' })(字符串)会被当成普通标签,不参与 sum / average。改成 { value: 99.9 }

同一个实验,刷新前后变体不一致

SDK 粘性缓存保证 60 天内变体稳定。出现不一致通常是:

  • localStorage 被清除(隐私模式 / 用户主动清空)
  • 后台开启了用户强制分配,会覆盖粘性缓存

实验在生产没生效,怎么排查

按顺序检查:

  1. storeId 是否正确(错误的 storeId 不报错,只是拿不到任何实验)
  2. 后台实验是否处于"运行中"状态(暂停 / 草稿状态不会下发)
  3. 用户是否命中实验定向规则(设备 / 国家由后端从 HTTP 请求头解析)
  4. getExperiments() 看返回结果,确认是不是变体决策本身就是对照组

我想让某个用户完全不参与实验

SDK 不提供"退出实验"接口。变通方案:业务代码自行判断(如登录态 / 角色),命中条件时不读 experiments 直接走默认渲染。

getExperiments() 为什么返回 null 而不是空对象

null 表示"未就绪"或"无任何命中实验",便于做存在性判断:

const all = ab.getExperiments();
if (!all) return renderDefault();

避免写 Object.keys(all).length === 0

React 服务端组件 / Edge Runtime 能用吗

不能。SDK 依赖 localStorage / document.cookie / window 等浏览器接口。


浏览器兼容性

依赖原生 fetch / Promise / localStorage。支持:

  • Chrome / Edge ≥ 最近 2 个大版本
  • Safari ≥ 最近 2 个大版本
  • Firefox ≥ 最近 2 个大版本

不支持 IE。SDK 不内置任何兼容垫片。

These developer docs retire on 2026-08-30. Visit the new docs: NEW DOCS本开发者文档将于 2026-08-30下线,请访问新版文档: 新文档