Skip to main content
These endpoints are live in production today, served by the FloKit payments gateway. They are separate from the v1 REST API, which is still in design-partner preview.
The paywall API powers remote paywalls: your app fetches a published paywall configuration (with deterministic A/B variant selection), submits store receipts for verification, and logs funnel events for paywall analytics. Base URL: https://payments-gateway.flokitai.com

Authentication

These endpoints use request-scoped credentials instead of server API keys:
  • x-app-key header — your app’s publishable key (pk_...), created per app in the FloKit dashboard. It scopes the request to your app and is safe to embed in your app build (it is not a secret and does not authenticate the user). Required on entitlement reads and funnel event writes; not needed for GET /api/paywall/config.
  • x-app-token header — an optional short-lived app-session token minted from the publishable key via POST /api/paywall/token. Anywhere x-app-key is accepted, x-app-token works instead — send one or the other.
  • x-user-id header — the user’s identity. userId or anonymousId query parameters are accepted as a fallback where custom headers are inconvenient (the header takes precedence).
Tenant resolution happens server-side from the app key and user identity — clients never send a company or tenant ID.
x-app-key enforcement is being phased in ahead of GA. Integrations should send it on every request today; once enforcement is on, requests without a valid key receive 401 on the endpoints marked as requiring it. @flokit/subscriptions-sdk v0.2.0+ sends it automatically via the appKey option.

GET /api/paywall/config

Returns the published paywall configuration for an app, with a single variant selected for the requesting user. Variant selection is deterministic — the same user always receives the same variant for a given paywall, and traffic splits respect each variant’s traffic_weight when an experiment is active.

Query parameters

app_id
string
required
Your application identifier.
paywall_id
string
required
The paywall to fetch, e.g. onboarding.
userId
string
User identity fallback if the x-user-id header is not set.
anonymousId
string
Anonymous identity fallback for pre-login users.

Example

curl "https://payments-gateway.flokitai.com/api/paywall/config?app_id=your_app&paywall_id=onboarding" \
  -H "x-user-id: usr_abc123"

Response

{
  "paywall_id": "onboarding",
  "version": 3,
  "variant": {
    "variant_id": "annual_emphasis",
    "layout": "single_offer",
    "offers": [
      {
        "product_id": "com.yourapp.annual",
        "name": "Annual",
        "price_usd_cents": 4999,
        "currency": "USD",
        "billing_period": "yearly",
        "trial_days": 7
      }
    ],
    "cta_copy": "Start your free trial"
  },
  "fallbackUsed": false
}
FieldTypeDescription
paywall_idstringThe requested paywall identifier
versionnumberPublished configuration version
variant.variant_idstringThe variant selected for this user
variant.layoutstringLayout hint, e.g. single_offer or standard
variant.offers[]arrayOffers to render (see below)
variant.cta_copystringOptional call-to-action copy
fallbackUsedbooleantrue when no published config was found and a safe empty fallback was returned
Each offer:
FieldTypeDescription
product_idstringStore product identifier
namestringDisplay name
price_usd_centsnumberPrice in minor units
currencystringISO 4217 currency code
billing_periodstringweekly, monthly, quarterly, yearly, or lifetime
trial_daysnumberFree trial length; 0 means no trial
If no published configuration exists for the requested paywall, the endpoint still returns 200 with a fallback: version: 0, variant control with an empty offers array, and fallbackUsed: true. Your app should treat an empty offer list as “do not show the paywall.” Responses are cacheable: Cache-Control: public, max-age=60, stale-while-revalidate=300.

Errors

StatusBodyCondition
400{ "error": "app_id and paywall_id are required." }Missing query parameter
401{ "error": "x-user-id header or userId/anonymousId query param is required." }No identity provided

POST /api/paywall/receipt

Submits an in-app purchase receipt for server-side verification. Send the Apple StoreKit 2 signed transaction (JWS) or the Google Play package name + purchase token; the gateway forwards it to the verification service and returns the verification result.

Headers

HeaderValue
x-user-idYour authenticated user ID (required)
x-app-keyYour publishable app key (recommended)
Content-Typeapplication/json
Receipt verification is protected by the store signature on the receipt itself, so x-app-key is not enforced here — but send it anyway for uniformity.

Request body

provider
string
required
apple or google.
signedTransactionInfo
string
Apple StoreKit 2 signed transaction (JWS). Required for apple.
packageName
string
Android package name. Required for google.
purchaseToken
string
Google Play purchase token. Required for google.

Example

curl -X POST https://payments-gateway.flokitai.com/api/paywall/receipt \
  -H "x-user-id: usr_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "apple",
    "signedTransactionInfo": "eyJhbGciOiJFUzI1NiIs..."
  }'

Response

The verification result is returned as produced by the verification service, for example:
{
  "active": true,
  "provider": "apple",
  "product_id": "com.yourapp.annual"
}

Errors

StatusBodyCondition
400{ "error": "Invalid receipt request." }Body fails validation
401{ "error": "x-user-id is required." }Missing user identity
422{ "error": "..." }Verification rejected upstream, e.g. no tenant provisioned for this user

POST /api/paywall/events

Logs paywall funnel events — impressions, offer taps, purchases, conversions, cancellations — for paywall analytics. Accepts 1–50 events per request. The gateway resolves the request’s country at the edge; a server-resolved country takes precedence over any client-supplied country value.

Headers

HeaderValue
x-app-keyYour publishable app key (required)
x-user-idYour authenticated user ID (required)
Content-Typeapplication/json
Every event’s app_id must belong to the app the key was issued for — mismatches are rejected with 403.

Request body

events
array
required
1–50 event objects.
Each event:
event_type
string
required
One of impression, offer_tap, purchase_start, trial_start, convert, cancel.
app_id
string
required
Your application identifier.
properties
object
required
Must include paywall_id and variant_id. Additional properties are passed through.
country
string
ISO 3166-1 alpha-2 country code. Used only if server-side resolution fails.
platform
string
Client platform, e.g. ios or android.
occurred_at
string
ISO 8601 timestamp of when the event occurred.

Example

curl -X POST https://payments-gateway.flokitai.com/api/paywall/events \
  -H "x-app-key: pk_live_your_app_key" \
  -H "x-user-id: usr_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "event_type": "impression",
        "app_id": "your_app",
        "platform": "ios",
        "occurred_at": "2026-06-21T10:30:00Z",
        "properties": {
          "paywall_id": "onboarding",
          "variant_id": "annual_emphasis"
        }
      }
    ]
  }'

Response

{
  "accepted": 1
}

Errors

StatusBodyCondition
400{ "error": "Invalid paywall events request." }Bad event_type, empty array, more than 50 events, or missing required fields
401{ "error": "x-app-key is required." }Missing app key (once enforcement is on)
401{ "error": "Invalid app key." }Unknown or revoked app key
401{ "error": "x-user-id is required." }Missing user identity
403{ "error": "event app_id does not match the app key." }An event claims an app_id the key was not issued for
422{ "error": "..." }Rejected upstream, e.g. no tenant attributed for this user

POST /api/paywall/token

Exchanges the publishable key for a short-lived app-session token bound to the requesting app and user. Use it when you’d rather not attach the raw pk_... key to every request: mint once at SDK init, send the token as x-app-token on subsequent calls, and re-mint when it expires.

Headers

HeaderValue
x-app-keyYour publishable app key (required — always enforced on this endpoint)
x-user-idYour authenticated user ID (required)

Example

curl -X POST https://payments-gateway.flokitai.com/api/paywall/token \
  -H "x-app-key: pk_live_your_app_key" \
  -H "x-user-id: usr_abc123"

Response

{
  "token": "v1.eyJhIjoi...",
  "token_type": "app-session",
  "expires_in": 900
}
The token is opaque to clients, expires after 15 minutes, and is only valid for the same x-user-id it was minted for — a token leaked from one user cannot read another user’s entitlements.

Errors

StatusBodyCondition
401{ "error": "x-app-key is required." } / { "error": "Invalid app key." }Missing or invalid key
401{ "error": "x-user-id is required." }Missing user identity
503{ "error": "App session tokens are not enabled." }Token minting not configured on this environment

Provider webhook passthrough

The gateway also exposes webhook receiver endpoints for subscription and attribution providers. Bodies are forwarded as raw bytes — provider signature headers are preserved and verified downstream, so configure these URLs directly in each provider’s dashboard.
EndpointProvider
POST /webhooks/stripeStripe (signature in stripe-signature header)
POST /webhooks/appleApple App Store Server Notifications V2
POST /webhooks/googleGoogle Play RTDN (Pub/Sub push)
POST /api/providers/revenuecat/webhookRevenueCat
POST /api/providers/adapty/webhookAdapty
POST /api/providers/generic-subscription/webhookGeneric subscription provider
POST /api/integrations/mmp/generic-attributionGeneric MMP attribution callback
If the downstream service cannot be reached, the gateway responds 502; if it times out (10 seconds), 504 — providers treat both as delivery failures and retry.