应用内收入,记在一本账上。
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"
} 签名已验证
已接入
- Apple App Store
- Google Play
- Stripe
- 测试商店
- AdMob
- AppLovin MAX
- Unity LevelPlay
Kasskeep 在哪一层
App 不再自己相信设备上的购买结果,而是交给 Kasskeep 验证。商店和广告网络把通知发给 Kasskeep,你的业务后端只对接这一处。
现在就能用的能力
验证做到商店级别
StoreKit 2 交易逐级校验到苹果根证书,Google 购买向 Play Developer API 回查,Stripe 对象一律回查 Stripe API 后才入账。
一套事件模型
购买、续订、取消、宽限期、退款、拒付、广告奖励,统一成 18 种事件类型,各渠道字段相同。
投递扛得住故障
回调失败会在约两天半里重试,最多投递 8 次。事件 id 稳定,去重很简单;还可以用 GET /v1/events 补拉。
每小时对账
每小时拉取苹果通知历史、Google 退款记录和 Stripe 事件,补上漏收的通知。
数据按账号隔离
每个账号的数据由 Postgres 行级安全隔开,商店凭据用账号专属的密钥加密。团队成员分角色,登录一律需要两步验证。
激励广告在服务端验证
AdMob SSV、AppLovin MAX、Unity LevelPlay 的回调逐条验签、去重,并映射到你自己起的广告位名。没配置过的广告位默认拒收。
从零到收到第一条签名事件
-
开通账号
自助注册的控制台即将上线。在此之前,我们按申请开通账号,并邀请你成为所有者。
-
建 App
一条 CLI 命令,得到给客户端的公钥、给业务后端的私钥和回调签名密钥。
-
接商店
上传 App Store 和 Play 的凭据,把各商店的服务器通知地址指向你的 Kasskeep。
-
接 SDK
用公钥配置 React Native SDK。交易先提交给 Kasskeep,成功后才结束。
-
处理事件
校验 Kasskeep-Signature 请求头,按事件 id 去重,在你自己的代码里发放或收回权益。
两端各写几行
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 里就能用,服务端还能把订阅直接快进到下一次续订。
从文档开始
文档里的每个接口、参数和事件都对照源码写成。
联系我们
有问题,或想在控制台上线前先开通账号,请来信: [email protected]