Quickstart
This guide takes you from a new account to the first signed event on your backend. Kasskeep is a hosted service: you do not run any servers for it. The API lives at https://api.kasskeep.com.
1. Get an account
Section titled “1. Get an account”The self-service console is coming soon. Until it launches, accounts are opened on request: contact us and we create your tenant and invite you as its owner.
The invitation contains a one-time token. Accept it and set your password, then enroll two-step verification. TOTP is mandatory for every member.
curl -X POST https://api.kasskeep.com/v1/admin/invitations/accept \ -H "Content-Type: application/json" \ -d '{"token": "<invitation token>", "password": "<your password>", "name": "Your Name"}'The response contains a session that can only enroll TOTP. Call POST /v1/admin/me/totp/setup, add the returned otpauth_uri to your authenticator app, and confirm with POST /v1/admin/me/totp/enable and {"code": "123456"}. Then sign in and create a personal access token for the CLI:
curl -X POST https://api.kasskeep.com/v1/admin/auth/login \ -H "Content-Type: application/json" \
curl -X POST https://api.kasskeep.com/v1/admin/me/tokens \ -H "Authorization: Bearer kks_..." -H "Content-Type: application/json" \ -d '{"name": "cli", "expires_in_days": 90}'The token (kkp_...) is shown once. Store it in your password manager. See Keys and access.
2. Create an app
Section titled “2. Create an app”Each product that sells something is an app in your tenant. The CLI runs with npx and needs Node.js 22 or later. Log in once with your token; it is saved in ~/.config/kasskeep/credentials:
npx @kasskeep/cli login # paste the kkp_... token when asked
npx @kasskeep/cli apps create \ --name "My App" \ --ios-bundle-id com.example.app \ --android-package com.example.app \ --webhook-url https://api.example.com/webhooks/kasskeepThe output contains three keys. They are shown once and cannot be read back later.
| Key | Where it goes |
|---|---|
public_key (pk_...) |
The app. It can submit receipts and read the user’s own state. |
secret_key (sk_...) |
Your backend only. It can read transaction details and the event log. |
webhook_secret (whsec_...) |
Your backend only. It verifies webhook signatures. |
Note the id (app_...): store notification URLs contain it.
3. Connect a store
Section titled “3. Connect a store”App Store
Section titled “App Store”- In App Store Connect, open Users and Access → Integrations → In-App Purchase, generate a key and download the
.p8file. Note the Key ID and Issuer ID. - Upload it. The CLI reads the file locally and sends it over HTTPS; Kasskeep encrypts it and never returns it.
npx @kasskeep/cli apps update --id app_xxx \ --apple-issuer-id <Issuer ID> --apple-key-id <Key ID> --apple-app-apple-id 1234567890 \ --apple-private-key-file ./AuthKey_XXXX.p8- In App Information → App Store Server Notifications, choose Version 2 and set both the production and sandbox URL to
https://api.kasskeep.com/v1/stores/apple/<app_id>/notifications.
Google Play
Section titled “Google Play”Create a service account, grant it access in Play Console, upload its JSON key and set up a Pub/Sub push subscription to https://api.kasskeep.com/v1/stores/google/<app_id>/rtdn. The Google Play page has every step.
No store yet?
Section titled “No store yet?”Turn on the test store and you can run the rest of this guide without any store account:
npx @kasskeep/cli test-store enable --id app_xxx4. Integrate the SDK
Section titled “4. Integrate the SDK”Install @kasskeep/react-native (see React Native and Expo) and configure it with the public key. appUserId is your own user id and must be a UUID.
import { Kasskeep } from "@kasskeep/react-native";
Kasskeep.configure({ apiKey: "pk_...", baseUrl: "https://api.kasskeep.com", appUserId: user.id, testStore: __DEV__,});
const result = await Kasskeep.purchase({ productId: "pro_monthly", type: "subs" });if (result.status === "completed") { console.log(result.customer.subscriptions);}The SDK sends the transaction to Kasskeep and only finishes it after Kasskeep has stored it. Unfinished transactions are submitted again on the next launch.
5. Receive webhooks
Section titled “5. Receive webhooks”Kasskeep POSTs every event to the app’s webhook_url. Verify the signature against the raw request body, deduplicate by event id, then act on it.
import express from "express";import { createHmac, timingSafeEqual } from "node:crypto";
const secret = process.env.KASSKEEP_WEBHOOK_SECRET;const app = express();
app.post("/webhooks/kasskeep", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("Kasskeep-Signature") ?? ""; const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2))); const t = Number(parts.t); if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400); const expected = createHmac("sha256", secret).update(`${parts.t}.`).update(req.body).digest(); const actual = Buffer.from(parts.v1 ?? "", "hex"); if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) return res.sendStatus(400);
const event = JSON.parse(req.body.toString("utf8")); if (event.type === "test") return res.sendStatus(200); if (alreadyHandled(event.id)) return res.sendStatus(200); handleEvent(event); res.sendStatus(200);});Send a signed test event to check the endpoint:
curl -X POST https://api.kasskeep.com/v1/admin/tenants/<tenant_id>/apps/<app_id>/webhooks/test \ -H "Authorization: Bearer kkp_..."The response shows the status code, timing and the first 500 bytes of your endpoint’s reply.
Next steps
Section titled “Next steps”- Event types and what to do with each.
- Environments: keep sandbox and test data out of your revenue.
- Signatures: test vectors for your verification code.