Purchase verification & licensing API

The server that vouches for your app's purchases.

Keywarden verifies Google Play and App Store purchases and hands the device a signed entitlement it can check offline. License keys and device activation live under the same roof. Nothing to run — add the SDK, ship.

POST /v1/store/verify · api.keywarden.dev
// request
{ "store": "play", "productId": "pro",
  "purchaseToken": "…", "device": { "installId": "…" } }

// response — signed, verifiable offline
{ "ok": true,
  "purchase": { "state": "active" },
  "bundle": "eyJ2IjoyLCJhcHAiOi….MEUCIQ…" }
IN PRODUCTIONVerifying purchases for KeyFlow on Google Play
SDKSAndroid 6+ (Kotlin & Java) · iOS 15+ and macOS 12+ (Swift)
SECURITYECDSA P-256 signed entitlements · keys never leave your app

Signed entitlements, verified on device

The server never says "you own it" — it signs it with ECDSA P-256. The app verifies the token offline, so editing a preferences file on a rooted device buys nothing.

Refunds are caught

Play's voided-purchases list is polled and refunded purchases drop on the next check. If the list is unreachable, paying users are never locked out — and the health endpoint says so.

Trials and support tiers, server-side

"Try it 3 times" counters live on the server, not in a local file. Optional support purchases add up and unlock a tier once a threshold is reached; amounts freeze at purchase time, so a later price change never rewrites history.

Integrate in an afternoon

Configure once. Ask anywhere.

Android · Kotlin (Java-friendly)
Keywarden.configure(
    Config.Builder(context)
        .apiKey(BuildConfig.KEYWARDEN_PK)
        .baseUrl("https://api.keywarden.dev")
        .publicKey("k1", SPKI_B64)   // pinned; verifies offline
        .build()
)

Keywarden.get().sync()
if (Keywarden.get().has("pro")) unlockPro()
iOS & macOS · Swift (StoreKit 2)
Keywarden.configure(.init(
    apiKey: Secrets.keywardenPK,
    baseURL: URL(string: "https://api.keywarden.dev")!,
    publicKeys: ["k1": spkiBase64]   // CryptoKit P-256, offline
))
Keywarden.enableStoreKit()

try await Keywarden.shared.sync()
if Keywarden.shared.has("pro") { unlockPro() }
Pricing

Simple, predictable pricing.

Prices in USD, billed via Paddle. VAT included, shown in your local currency. Two months free on annual plans.

FREE
$0/ month

1 app · 100 users. Store verification, signed entitlements, trial counters, dashboard. Ship your first paid feature.

Create account →
PRO · MOST POPULAR
$19/ month

10 apps · 50,000 users. License keys and device activation, webhooks, e-mail support.

Start Pro →
BUSINESS
$39/ month

25 apps · 200,000 users. Paddle and Lemon Squeezy integrations, 3 team members, priority support.

Start Business →
TEAM
$79/ month

Unlimited apps · 500,000 users. Unlimited members with roles, audit log, uptime SLA.

Talk to us →