kasskeep

应用内收入,记在一本账上。

Kasskeep 替你验证 App Store、Google Play 和 Stripe 的购买,跟踪每一条订阅的续订、退款和过期,确认用户确实看完了激励广告,再用同一种签名事件通知你的业务后端。这是托管服务,你不需要运行任何服务器。

POST /webhooks/kasskeep
Kasskeep-Event-Type: subscription.renewed
Kasskeep-Signature: t=1767225600,v1=5257a869e7ec…08d8bd
{
  "id": "evt_3f0c8a91d2b74e6f",
  "type": "subscription.renewed",
  "app_user_id": "6f1c2a3b-4d5e-4f60-8a71-92b3c4d5e6f7",
  "platform": "ios",
  "environment": "production",
  "product_id": "pro_monthly",
  "expires_at": "2026-12-01T12:00:00.000Z",
  "price_micros": 9990000,
  "currency": "USD"
}

签名已验证

所有商店、所有广告网络,都是同一种事件格式。用 HMAC-SHA256 签名,你的服务器返回 2xx 之前会一直重试。

已接入

  • Apple App Store
  • Google Play
  • Stripe
  • 测试商店
  • AdMob
  • AppLovin MAX
  • Unity LevelPlay

Kasskeep 在哪一层

App 不再自己相信设备上的购买结果,而是交给 Kasskeep 验证。商店和广告网络把通知发给 Kasskeep,你的业务后端只对接这一处。

你的 App React Native SDK、WebView 桥、网页脚本
提交凭证
Kasskeep 验证、去重、入账、对账
签名事件
你的业务后端 签名回调、事件拉取、用户查询
服务器通知
商店与广告网络 服务器通知、RTDN、Stripe webhook、激励广告 S2S 回调

现在就能用的能力

验证做到商店级别

StoreKit 2 交易逐级校验到苹果根证书,Google 购买向 Play Developer API 回查,Stripe 对象一律回查 Stripe API 后才入账。

一套事件模型

购买、续订、取消、宽限期、退款、拒付、广告奖励,统一成 18 种事件类型,各渠道字段相同。

投递扛得住故障

回调失败会在约两天半里重试,最多投递 8 次。事件 id 稳定,去重很简单;还可以用 GET /v1/events 补拉。

每小时对账

每小时拉取苹果通知历史、Google 退款记录和 Stripe 事件,补上漏收的通知。

数据按账号隔离

每个账号的数据由 Postgres 行级安全隔开,商店凭据用账号专属的密钥加密。团队成员分角色,登录一律需要两步验证。

激励广告在服务端验证

AdMob SSV、AppLovin MAX、Unity LevelPlay 的回调逐条验签、去重,并映射到你自己起的广告位名。没配置过的广告位默认拒收。

查看全部功能

从零到收到第一条签名事件

  1. 开通账号

    自助注册的控制台即将上线。在此之前,我们按申请开通账号,并邀请你成为所有者。

  2. 建 App

    一条 CLI 命令,得到给客户端的公钥、给业务后端的私钥和回调签名密钥。

  3. 接商店

    上传 App Store 和 Play 的凭据,把各商店的服务器通知地址指向你的 Kasskeep。

  4. 接 SDK

    用公钥配置 React Native SDK。交易先提交给 Kasskeep,成功后才结束。

  5. 处理事件

    校验 Kasskeep-Signature 请求头,按事件 id 去重,在你自己的代码里发放或收回权益。

两端各写几行

App 里
import { Kasskeep } from "@kasskeep/react-native";

Kasskeep.configure({
  apiKey: "pk_...",
  baseUrl: "https://api.kasskeep.com",
  appUserId: user.id,
});

const result = await Kasskeep.purchase({
  productId: "pro_monthly",
  type: "subs",
});
if (result.status === "completed") showPro(result.customer);
你的服务器上
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, header, rawBody) {
  const p = Object.fromEntries(
    header.split(",").map((part) => part.split("=")),
  );
  const age = Math.abs(Date.now() / 1000 - Number(p.t));
  if (age > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(`${p.t}.${rawBody}`)
    .digest();
  const actual = Buffer.from(p.v1, "hex");
  return actual.length === expected.length
    && timingSafeEqual(actual, expected);
}

不接真商店也能跑通全流程

内置的测试商店让购买、续订、宽限期、退款、撤销走和真实商店完全相同的链路,数据标记为 environment: test。Expo Go 里就能用,服务端还能把订阅直接快进到下一次续订。

了解测试商店