PostHog

Adapty 可以将订阅事件(购买、续订、退款、试用开始)传递到 PostHog 的事件流,与你的其他数据合并分析。

每个事件都包含收入、货币、渠道和产品详情,以及促成购买的付费墙和 A/B 测试实验变体信息。数据到达后,任何 PostHog 工具都可以直接使用。

Adapty 的订阅事件为你在 PostHog 中已追踪的所有数据增加了收入维度:

  • 哪些应用内行为能预测订阅? 将你自己的 PostHog 事件与 trial_startedsubscription_started 进行关联,找出导致购买的用户行为。
  • 哪个实验变体带来了更多收入? 以 Adapty 的收入事件作为 PostHog 实验的衡量指标,用于评估任何产品变更。Adapty 的 A/B 测试 涵盖付费墙和用户引导。
  • 退款前发生了什么? 基于 subscription_refunded 构建洞察分析,然后打开相关用户,查看其会话录制。
  • Bug 会影响续订吗? 将错误追踪与 subscription_renewal_cancelled 进行交叉对比——该事件在用户关闭自动续订时立即触发,早于 subscription_expired
  • 订阅用户与免费用户的留存有何差异? 基于推断的访问状态构建同期群和留存曲线——参见如何区分订阅用户和免费用户
  • 哪个付费墙和实验变体带来了收入? 购买事件会携带产生该购买的付费墙和实验变体信息,因此你可以按付费墙拆分收入。使用 PostHog 的 SDK 捕获付费墙曝光事件,以便同时衡量曝光到购买的转化率。
  • 跨两个数据集提问,使用洞察分析或 SQL 均可。

集成工作原理

PostHog 通过一个叫做 distinct_id 的字符串来识别每个用户。整个集成能否正常运行,取决于 Adapty 与 PostHog 使用相同的 distinct_id 字符串。

  1. PostHog SDK 会为用户分配一个 distinct_id。首次启动时,这个字符串是设备范围内的匿名标识。你可以在登录时自定义其值,也可以在退出登录时将其重置。
  2. 每次应用启动时,在调用 Adapty.activate() 之后、用户发生任何购买行为之前,通过 setIntegrationIdentifier()distinct_id 传递给 Adapty。Adapty 会将该值作为 posthog_distinct_user_id 附加到用户的 Adapty 用户画像中。
  3. 当用户开始试用或完成购买时,Adapty 服务器会将该事件连同 distinct_id 一起通过 PostHog 的 capture API 上报。整个交互过程在服务器之间完成,不经过你的应用。
  4. PostHog 会将事件关联到持有该 distinct_id 的用户,这样订阅收入数据就会与该用户在应用内的所有其他行为数据一起呈现。

将 Adapty 的 ID 与 PostHog 的匹配

两个系统必须对同一用户使用相同的 distinct_id,否则同一个人会被拆分成两个互不关联的实体。为避免这种情况,你有两种方式,具体选哪种取决于购买行为发生的时机。

如果购买需要登录,在用户登录时调用 PostHog 的 identify(),并传入你的 Customer User ID。Adapty 在没有其他信息时默认使用该值,因此两个系统可以自动匹配。

如果允许匿名用户购买,请读取 PostHog 的 distinct_id 并通过 setIntegrationIdentifier 传递给 Adapty。在每次启动时于 Adapty.activate() 之后调用,并在每次 PostHog 执行 reset() 后再次调用。匿名用户画像没有 Customer User ID,因此 PostHog 自身的 ID 是两个系统之间唯一可以共享的值——详见配置应用代码

无论采用哪种方式,该值都必须在重装后保持不变。Adapty 每次重装都会创建新的用户画像,PostHog 每台设备也会生成新的匿名 distinct_id,因此两个系统本身都无法跨越这道断层来维持用户身份。Adapty 确实能在用户的匿名用户画像之间传递付费访问权限,但那只是关联了访问等级,而非分析身份——继承的用户画像仍会以自己的 ID 发送事件。如果没有一个由你自己的后端管理的稳定 ID,重装后的订阅者在 PostHog 中会被识别为一个全新用户,只有续订记录,没有任何在此之前的购买记录。

Warning

PostHog 在合并用户时会屏蔽某些特定值:nullundefinedNone0anonymousguestdistinct_ididemailtruefalse[object Object]NaN、空字符串,以及这些值的带引号变体。请确保你传入的值永远不会是其中之一。PostHog 建议使用 UUID,或在发送前对照上述列表进行校验。详见 PostHog 身份识别指南

ID 不匹配会导致数据碎片化——详见一个用户在 PostHog 中显示为多个用户

Important

Adapty 的事件携带用户属性,这使得每个事件在 PostHog 中都属于已识别事件——PostHog 处理这类事件的费用最高是匿名事件的 4 倍。Adapty 每次订阅生命周期变更只发送一个事件,因此与客户端分析相比,数据量很小。即便如此,保持 ID 一致仍然值得:PostHog 在绝大多数情况下都建议对用户进行识别。

设置说明

复制您的 PostHog 项目 Token

  1. 登录托管您数据的部署环境——美国云欧洲云——并选择您要连接的项目。

  2. 前往 Settings > Project > General,找到 Project token & ID 部分。

    PostHog 项目设置,显示 Project token、Project ID 和 Region
  3. 复制 Project token。它以 phc_ 开头。PostHog 将其描述为只写且可安全公开,因此您无需轮换它。

配置 Adapty

  1. 在 Adapty 看板中打开 Integrations > PostHog

  2. 启用 PostHog 开关。

  3. 将 token 粘贴到 Project API key 中。Adapty 在保存时会通过 PostHog 验证该 token——如果 token 无效,会立即报错,而不是悄无声息地丢弃事件。

  4. “server location” 部分取决于你的配置情况。

  • 如果您使用 PostHog Cloud,请将 Region 设置为您登录的部署环境——US CloudEU Cloud。将 PostHog Instance URL 字段留空。
    • 如果您运行 自己的实例,请选择 Self-hosted 并填写 PostHog Instance URL 字段。不要选择 region。Adapty 必须能够在不使用代理/VPN 的情况下访问该实例。
  1. How the revenue data should be send 下,选择 Adapty 发送的收入数据类型。三个选项与 Adapty Analytics 收入视图对应,因此你的选择也决定了 PostHog 中的数据应与哪个视图保持一致。
选项Adapty 发送的内容
Gross revenue买家支付的全额金额,扣除佣金和税款前。默认选项。
Proceeds after store commission扣除商店佣金后的金额,仍包含税款。
Proceeds after store commission and taxes同时扣除佣金和税款后的金额。
  1. 设置其余选项:
开关开启时的效果默认值
Report user’s currencyAdapty 以买家实际支付的货币上报每笔销售额,而非美元。
Send trial price试用开始事件默认不携带收入数据。开启此选项可为每次试用设置一个占位价格,同时会出现 Trial price percentage 字段——设置试用应上报的订阅价格百分比。设为 60% 时,一个 $10 的订阅将上报 $6。
Exclude historical eventsAdapty 跳过用户安装包含 Adapty SDK 的版本之前发生的事件。
  1. Events names 区域重命名或禁用单个事件。PostHog 接受任意非空事件名称,可按照您现有的命名规范自由设置。

  2. 点击 Save

Adapty 看板 PostHog 集成页面,包含开关、密钥、服务器位置、收入设置和事件名称

将沙盒数据与生产环境隔离

Adapty 通过同一个集成渠道发送沙盒和生产环境的交易数据,因此两者都会落入同一个 PostHog 项目。该集成只使用一个 Project API key,没有单独的沙盒密钥可供指向其他项目。

为开发构建单独创建一个 PostHog 项目,也无法将沙盒事件从生产项目中剔除。沙盒是商店交易本身的属性,与构建版本无关。App Store 审核和 TestFlight 的购买,都属于来自生产构建的沙盒交易。

相反,可以通过查询条件来区分两者。每个事件都包含一个 environment 属性,值为 SandboxProduction。通过过滤来隔离实际收入:

WHERE properties.environment = 'Production'

配置应用代码

  1. 向 PostHog SDK 获取当前的 distinct_id

    • 每次应用启动时,在调用 Adapty.activate() 之后、任何购买发生之前。PostHog 会将更早发生的事件归因到另一个用户
    • 在 PostHog 的 reset() 之后,大多数应用会在退出登录时调用此方法。reset() 会生成一个新的匿名 ID,且不会将其与之前的用户关联,因此过期的值会继续指向刚刚登出的用户。
  2. 通过 setIntegrationIdentifier() 将其传递给 Adapty。

Note

调用 PostHog 的 identify() 后无需额外操作。PostHog 会将匿名用户与已识别用户合并,因此 Adapty 事件的解析方式不变。详见将 Adapty 的 ID 与 PostHog 匹配

Note

第三方 SDK 会异步生成用户 ID,在 Adapty.activate() 执行时该 ID 可能尚未就绪。如果你的 Customer User ID 来自此类 SDK,请先不带该 ID 调用 Adapty.activate()。待 ID 到位后,依次调用 setIntegrationIdentifier(),再用 CUID 调用 identify()

验证集成

  1. 触发一次沙盒购买,然后在 Adapty 看板中打开 Event Feed。每次发送尝试都会显示其结果。Adapty 在你保存时会验证你的 Project API key 和实例 URL,因此此阶段出现故障的情况较少见。常见原因如下:

    • 密钥失效。 如果你删除了集成密钥,PostHog 会返回 401
    • 实例无响应。 Adapty 最多等待 10 秒。可能影响自托管部署。

    将鼠标悬停在失败的事件上,即可查看 PostHog 的响应内容。

  2. 在 PostHog 中,打开 Activity 视图并查找该事件。你的购买记录的 environment 属性应为 Sandbox

  3. 打开该用户的画像,并查看 Distinct IDs 标签。你的应用自有事件与 Adapty 的服务端事件应归属于同一个用户。如果同一用户出现了两个人,说明 ID 不匹配——请参阅同一用户在 PostHog 中显示为多个用户

Note

Adapty 的事件不会出现在你的应用自身的 PostHog 调试输出中。Adapty 从其服务器直接发送这些事件,不经过你的应用 SDK。本地日志为空并不代表集成有问题。

在 PostHog 中上报收入

Adapty Analytics 始终是收入数据的权威来源,因为它基于完整的应用商店数据进行计算,而 PostHog 只能接收本次集成转发的内容。如果你还希望在 PostHog 看板中将收入与产品指标一并展示,PostHog 的 Revenue Analytics 支持从你指定的事件属性中读取收入数据。在 PostHog 中打开 Data management > Revenue 并完成映射:

PostHog 字段Adapty 属性
Revenueprice_usdproceeds_usdnet_revenue_usd — 与您在 如何发送收入数据 中选择的选项保持一致
Currencycurrency,或者如果您以美元报告,可设置静态货币
Productvendor_product_id
Subscriptionoriginal_transaction_id

请将 PostHog 的”values are in cents”选项保持关闭状态。Adapty 发送的是十进制金额,而非最小单位。

PostHog 事件结构

Adapty 会发送你在 PostHog 集成页面Events names 部分启用的事件,每个事件对应一条捕获请求:

{
  "api_key": "phc_YOUR_PROJECT_TOKEN",
  "distinct_id": "john.doe@example.com",
  "timestamp": "2026-01-08T11:06:12+00:00",
  "event": "subscription_started",
  "properties": {
    "$ip": "10.168.1.1",
    "$geoip_time_zone": "America/New_York",
    "$geoip_disable": true,
    "$set": {
      "email": "user@example.com",
      "first_name": "John",
      "last_name": "Doe",
      "birthday": "1990-01-01",
      "gender": "male",
      "os": "iOS"
    },
    "*": "{{other_event_properties}}"
  }
}
参数类型描述
api_keyString您的 PostHog Project API key
distinct_idString用于在 PostHog 中标识用户。Adapty 使用第一个找到的值——请参阅 distinct ID 优先级
timestampISO 8601 日期和时间事件发生的时间。续订和试用转换的时间可能是未来时间——请参阅 事件在 PostHog 中提前出现
eventString您在 Events names 部分设置的名称。
propertiesObjectAdapty 的事件属性IP 和位置属性以及 $set。Adapty 会忽略没有值的属性。

仅限 Webhook 的五个属性不会出现在此处——请参阅限制

Distinct ID 优先级

Adapty 使用检测到的第一个值:

优先级设置方
1posthog_distinct_user_id你的 setIntegrationIdentifier 调用
2Customer User IDAdapty.activate()Adapty.identify()
3Adapty 内部用户画像 IDAdapty,始终存在

Adapty 会针对每个事件单独解析此优先级顺序,而非针对每个用户解析一次。如果某个事件在你调用 setIntegrationIdentifier 之前触发,它将以较低优先级的 ID 记录,PostHog 会为同一用户创建第二个用户记录。

将 Adapty 的 ID 匹配到 PostHog 中选择一种配置方式,然后在任何事件触发之前完成设置。对于已经分裂的用户,请参阅同一用户在 PostHog 中显示为多个用户画像

IP 与位置属性

如需按地理位置对 Adapty 事件进行市场细分,请使用 store_countryprofile_country 属性。Adapty 会关闭 PostHog 的位置查询功能,因此 PostHog 不会自动添加任何 $geoip_* 值。以下三个属性仅作用于单个事件,不影响你的项目设置和自定义事件。

属性效果
$ip用户的 IP 地址PostHog 将其存储在事件中。Adapty 以 x-forwarded-for 请求头的形式发送相同的值。
$geoip_time_zone用户的时区Adapty 直接设置此值。
$geoip_disable始终为 true关闭 PostHog 对该事件的地理位置查询。

人员属性

$set 内的所有内容都会成为 PostHog 人员属性,而不是事件属性。PostHog 将人员属性附加到用户本身,而不是某个单独的事件,因此它们描述的是用户的当前状态,而不是某一时刻的快照。Adapty 会省略没有值的字段,当所有字段均无值时,会完全去掉 $set

参数类型描述
emailString用户的电子邮件地址。
first_nameString用户的名字。
last_nameString用户的姓氏。
birthdayString (date)用户的出生日期。
genderString用户的性别。
osString用户设备的操作系统。

限制

  • 无访问等级或订阅状态。 access_level_updated 事件仅适用于 webhook 集成,因此 Adapty 不会向 PostHog 发送任何描述用户当前访问权限的字段——请参阅如何区分订阅用户与免费用户
  • 无历史数据回填。 Adapty 仅从你启用集成的那一刻起开始转发事件,历史购买记录不会同步到 PostHog。
  • PostHog 不对 Adapty 事件进行地理位置标记。 PostHog 通常根据事件来源的 IP 地址推断用户位置,但 Adapty 事件的来源始终是 Adapty 服务器,而非用户设备。为避免数据污染,Adapty 会告知 PostHog 跳过地理位置查询。Adapty 会填充 $geoip_time_zonestore_countryprofile_country,但不提供更精细的位置数据。
  • 无法按来源筛选 Adapty 事件。 PostHog 通过 $lib 属性记录发送方 SDK(如 posthog-iosposthog-androidweb),但 Adapty 是直接调用 PostHog API 发送数据,不经过任何 SDK,因此该属性始终为空。请改用事件名称进行筛选。

故障排查

事件未出现在 PostHog 中

  • 首先查看 Adapty 的 Event Feed。投递失败时会显示 PostHog 返回的错误信息。
  • 投递成功并不代表 PostHog 保留了该事件。只要 payload 和 key 有效,PostHog 就会返回 200 OK,但随后会静默丢弃没有名称或 distinct_id 为空的事件。
  • 确认你要查找的事件已在集成设置中启用。
  • 如果你自托管 PostHog,请确保服务器允许 Adapty 向 /capture 发送 POST 请求。配置成功并不代表该访问权限一定存在——Adapty 使用不同的端点来验证你的 key 是否有效。

事件动态中 access_level_updated 显示为失败

access_level_updated 是一个仅限 Webhook 的事件。Adapty 不会将其发送到此集成。但 Adapty 会为每个已启用的集成记录结果,不支持的事件将显示为失败。

一个用户在 PostHog 中显示为多个不同的人

PostHog 无法对大多数拆分进行事后撤销——请参阅将 Adapty 的 ID 与 PostHog 进行匹配

ID 出现差异的原因

Adapty 发送的每个事件都携带来自用户 Adapty 用户画像的 distinct_id——参见 Distinct ID 优先级。Adapty 在发送事件时读取该值,而非在你的应用调用 setIntegrationIdentifier 时读取。如果 Adapty 事件的 distinct_id 与应用安装的内部 distinct_id 不同,PostHog 会将这两类事件归属于两个不同的用户。

导致不匹配的原因有以下三种:

  1. 您的应用在某个平台上从未调用 setIntegrationIdentifier Adapty 会回退到 Customer User ID 或匿名用户画像 ID。请检查您发布的每个平台。
  2. setIntegrationIdentifier 的调用时机过晚。 在调用之前发生的订阅事件将携带回退 ID。
  3. 您通过 identify() 发送给 PostHog 的 ID 与您设置的集成标识符不一致。 Adapty 只保留您最后一次传入的值,不会自动更新。PostHog 的 reset() 会分配一个新的匿名 ID,而 Adapty 仍保留之前的值——因此每次调用 reset() 后都需要重新调用 setIntegrationIdentifier

修复以上三点后,PostHog 就会为每个用户只记录一个身份。

在首次调用 identify() 时合并分散的用户 ID

你的应用只有一次机会来协调这些分散的 ID:也就是首次调用 PostHog 的 identify() 时。PostHog 会将应用安装产生的事件合并到你在该调用中指定的用户身份下,因此请使用 Adapty 发送的 ID——详见将 Adapty 的 ID 与 PostHog 匹配。调用完成后,PostHog 会将该应用安装标记为已识别状态,并拒绝合并两个已识别的用户身份。

检查被拒绝的合并

要确认 PostHog 将同一用户记录为两个独立人员,请在 PostHog 中打开 Data management > Ingestion warnings,并查找 Refused to merge an already identified user 错误。

当 ID 是 PostHog 的保留值之一时,PostHog 也会阻止合并——保留值列表请参见将 Adapty 的 ID 与 PostHog 匹配

修复已存在的用户拆分

identify()alias() 都无法在合并窗口关闭后恢复已分裂的用户。只有 PostHog 的 $merge_dangerously 才能强制合并。PostHog 官方文档将其描述为不可逆操作,没有任何保护机制,仅用于一次性修复实现层面的问题。

该操作以事件形式发送(而非设置项),需要指定两个用户。合并方向决定哪一个用户最终保留:

字段
distinct_id合并后保留的用户
properties.alias被合并的用户——其事件和 distinct_id 将转移到保留方

在发送任何请求之前,先确定哪一方作为保留方。Adapty 用户画像保存了订阅历史,而您应用的用户画像保存了应用内行为数据。先对单个用户进行测试并验证结果,再进行批量修复。PostHog 的 How to merge users 提供了各 SDK 对应的请求载荷。

PostHog 中的收入与 Adapty 数据图表不匹配

Adapty 和 PostHog 对相同事件的处理方式不同,几乎所有数据差异都源于此。

  • 每个 Adapty 事件都包含三种收入金额,分别对应 收入数据的发送方式 中的三个选项。 以下是它们与 PostHog 属性的对应关系:
Adapty Analytics事件属性(美元)事件属性(买家货币)
毛收入price_usdprice_local
扣除平台佣金后的收益proceeds_usdproceeds_local
扣除平台佣金和税费后的净收益net_revenue_usdnet_revenue_local

跨行对比会产生差值,该差值等于佣金、税费或两者之和。无论 Report user’s currency 如何设置,Adapty 都会在每个事件中发送全部六个属性。

  • Adapty 会统计某一时段内的每一笔收入事件,而 PostHog 的数据洞察只统计你添加进去的事件。 如果漏加 subscription_renewed,对于任何成熟的应用来说,大部分收入都会丢失。

  • Adapty 的日期范围覆盖最后一天的全天;而 timestamp 过滤器只精确到你指定的那一刻。 Adapty 的 7月1日 – 7月15日,包含截止到 7月15日 23:59:59 的所有事件。在 PostHog 中,timestamp < 2026-07-15 会丢弃整个7月15日——应改用 timestamp < 2026-07-16

  • Adapty Analytics 将沙盒与生产环境分开;PostHog 会混合两者。 请在 properties.environment = 'Production' 上过滤 — 参见将沙盒数据排除在生产环境之外

  • Adapty 使用应用的报告时区;PostHog 接收的是 UTC。 无论你在 App Settings 中如何设置,集成始终传输 UTC 时间戳。例如,UTC 时间 7 月 1 日 23:30 发生的购买,在时区为 +02:00 的 Adapty 中会显示为 7 月 2 日,而 PostHog 仍保留为 7 月 1 日。

  • PostHog 中缺少历史事件。 有两个独立的限制会阻止旧事件传入,因此较早的订阅和续订只能在 Adapty Analytics 中查看。你自己的 PostHog SDK 所采集的事件不受影响。

  • 排除历史事件PostHog 集成页面 上的一个开关,默认开启。Adapty 不会发送早于用户画像创建时间的事件,且 Event Feed 会将每条此类事件标记为已过期。因此,用户的 Adapty 事件从其首次启动集成了 Adapty 的版本时开始记录。关闭此开关后,从该时间点起的回溯事件将被允许通过。

    • 无历史数据回填是一项永久限制。Adapty 在处理事件时实时发送,不会补发在您启用集成之前已处理的事件。
  • 在集成设置中禁用的事件永远不会发送到 PostHog。 Adapty Analytics 仍会统计这些事件。请查看配置 Adapty 中的 Events names 部分。

事件在发生前就出现在 PostHog 中

对于续订和试用转化,Apple 会在事件发生前提前通知 Adapty。Adapty 会立即转发这些事件,保持未来的 timestamp 不变。Adapty Analytics 会等到该时间到达后才展示,因此 PostHog 中显示的事件可能是 Adapty 尚未上报的。两者均正确。可通过过滤 timestamp 来排除这些事件——详见带未来日期的事件时间戳

Adapty 事件中无国家或城市数据

请改用 store_countryprofile_country 属性。PostHog 自身的 $geoip_* 值在 Adapty 事件中始终为空——详见IP 与位置属性

如何区分订阅用户与免费用户

Adapty 事件报告不会披露用户当前的访问权限。$set 仅包含 emailfirst_namelast_namebirthdaygenderos。描述访问等级的事件属性只在 access_level_updated 中才会有值,而 Adapty 仅通过 webhook 集成发送该事件。

有两种解决方案:

  • 根据事件历史在 PostHog 中推断状态。最近一条 Adapty 事件为 subscription_startedsubscription_renewedtrial_converted 的用户当前拥有访问权限;最近一条事件为 subscription_expiredtrial_expiredsubscription_refunded 的用户则没有。
  • 使用 webhook 集成 接收 access_level_updated,然后自行将其转发到 PostHog。PostHog 的 capture API 需要特定的数据格式,因此你需要在自己这边进行转换——Adapty 的 webhook 数据包不能直接发送到 PostHog。

付费墙视图未出现在 PostHog 中

Adapty SDK 仅为 Adapty Analytics 采集付费墙、流程和用户引导的交互数据。Adapty 服务器会将订阅事件转发给各集成渠道,但这些交互不属于订阅事件,Webhook 也不包含它们。请在展示付费墙的位置使用 PostHog SDK 自行采集。