Which endpoints do the RevenueCat SDKs call on RevenueDot?

The RevenueCat SDKs call these endpoints when their proxy URL points at RevenueDot. They take a public app key (appl_, goog_, test_ ...), which is safe to ship in an app. Several also accept a secret key for server-side use. Errors use the SDK's error format { "code": 7103, "message": "..." }; see Errors. Responses under /v1 and /rcbilling are signed when the server has a signing key; see Trusted Entitlements.

Base URL: your server, for example http://localhost:8787 or https://revenuedot.example.com. The examples read REVENUEDOT_URL, PUBLIC_KEY, SECRET_KEY and PROJECT_ID from your shell.

Operations on this page (74)#

Server#

Health and server info. No API key.

GET / · Auth: none

Answers a small JSON document. The Docker health check calls it.

Example request

Shell
curl -s "$REVENUEDOT_URL/"

Responses

  • 200: Server info.

Example 200 response:

JSON
{
  "name": "RevenueDot",
  "docs": "https://revenuedot.app/docs"
}

Health check#

GET /v1/health · Auth: none

Needs no API key. Use it for load balancer and uptime checks.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/health"

Responses

  • 200: The server is up.

Example 200 response:

JSON
{
  "status": "ok"
}

Connectivity probe#

GET /v1/health/connectivity · Auth: none

Needs no API key. The iOS SDK probes it only with its internal API failover setting, which is off by default.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/health/connectivity"

Responses

  • 200: The server is up.

Example 200 response:

JSON
{
  "status": "ok"
}

Customer info#

The customer's entitlements, subscriptions and one-time purchases, as the SDK decodes them into CustomerInfo.

Get customer info#

GET /v1/subscribers/{app_user_id} · Auth: public app key or secret key

What Purchases.getCustomerInfo() calls. Creates the customer when the app user id is new (answer 201). With a secret key the answer also has subscriber_attributes. Entitlements are listed even after they expire; an entitlement is active while expires_date is null or in the future.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Headers

Name Type Required Description
X-Nonce string no Base64 nonce the SDK sends when entitlement verification is on; it is part of the signed message.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Customer info. Returns CustomerInfo.
  • 201: Customer info of a customer created by this call. Returns CustomerInfo.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}

Receipts#

Purchases, restores and syncs. RevenueDot verifies them with the store.

Post a purchase or restore#

POST /v1/receipts · Auth: public app key or secret key

Every purchase, restore and syncPurchases() ends here. RevenueDot verifies the purchase with the store, saves it, records events and answers the updated customer info.

  • App Store: fetch_token is a StoreKit 2 signed transaction (JWS), a StoreKit 1 app receipt (base64) or an Xcode StoreKit test receipt. With the app's in-app purchase key, Apple's App Store Server API supplies the full history and renewal state.
  • Google Play: fetch_token is the purchase token. RevenueDot checks it with the Play Developer API and acknowledges it.
  • Amazon Appstore: fetch_token is the receipt id and store_user_id the Amazon user id (X-Platform: amazon). RevenueDot checks both with Amazon's Receipt Verification Service.
  • Stripe: from your backend, with X-Platform: stripe and the Stripe app's public key (strp_): fetch_token is a subscription id (sub_…) or a Checkout Session id (cs_…). RevenueDot reads it from Stripe with the app's restricted key. An unpaid first invoice or an open session answers 503, so post it again later.
  • Paddle: from your backend, with X-Platform: paddle and the Paddle app's public key (pdl_): fetch_token is a subscription id (sub_…) or a transaction id (txn_…). RevenueDot reads it from Paddle with the app's API key; a checkout that is not finished answers 503. Products are Paddle price ids (pri_…).
  • Roku: the Roku SDK posts fetch_token = the Roku transaction id (X-Platform: roku). RevenueDot validates it with Roku Pay's web services, which also supply the price and currency; X-Is-Sandbox: true (a sideloaded channel) records sandbox.
  • Galaxy Store: the Android SDK built with purchases-store-galaxy posts fetch_token = Samsung's purchase id with X-Platform: android and the Galaxy app's galx_ key. RevenueDot reads the receipt from Samsung, and a subscription's state with the app's service account; purchased_products tells the SDK which items to consume.
  • Test Store: fetch_token is test_<purchase time in ms>_<id>. Any such token is accepted.

4xx or 5xx matters. A 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. RevenueDot answers 5xx for its own and the store's temporary failures so the SDK keeps the purchase and retries. With a secret key, send X-Platform so RevenueDot knows which app the receipt belongs to.

Headers

Name Type Required Description
X-Platform string no SDK platform (ios, android, macos, web ...). With a secret key it picks the project's app for that platform.
X-Nonce string no Base64 nonce the SDK sends when entitlement verification is on; it is part of the signed message.
X-Is-Sandbox string no true when the SDK knows the purchase is sandbox (used for StoreKit 1 receipts without an environment).

Request body (application/json)

Field Type Required Description
app_user_id string yes The customer posting the receipt.
fetch_token string no Receipt, signed transaction, purchase token or Test Store token.
app_transaction string no StoreKit 2 app transaction JWS (accepted; not required).
transaction_id string no Store transaction id.
product_id string no Product being bought.
product_ids array of string no
platform_product_ids array of object no
platform_product_ids[].product_id string no
platform_product_ids[].base_plan_id string no
platform_product_ids[].offer_id string no
price number no
currency string no
store_country string no
normal_duration string no ISO 8601 period of the product.
is_restore boolean no
store_user_id string no
presented_offering_identifier string no Offering the purchase was made from; it appears in webhooks.
attributes object no Customer attributes to save with the purchase.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/receipts" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1","fetch_token":"test_1790800914000_quickstart","product_id":"pro_monthly","price":9.99,"currency":"USD","presented_offering_identifier":"default"}'

Responses

  • 200: Updated customer info, plus purchased_products. Returns ReceiptResponse.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.
  • 500: Server error. The SDK keeps the purchase and retries. Returns V1Error.
  • 503: The store could not be reached. Retry later. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  },
  "purchased_products": {
    "pro_monthly": {
      "should_consume": false
    }
  }
}

Offerings (SDK)#

What the paywall shows.

Get offerings#

GET /v1/subscribers/{app_user_id}/offerings · Auth: public app key or secret key

What Purchases.getOfferings() calls. Lists active offerings with the packages whose product belongs to the calling app. A new app user id makes the customer here, as GET /v1/subscribers/{app_user_id} does (the SDK sends both at once on a first launch), so the first answer already includes an experiment variant. current_offering_id is the customer's override when one is set. Otherwise RevenueDot resolves, in order: the experiment the customer is in (running or paused), a running experiment that enrolls them now (by priority), the first live targeting rule that matches, the project's current offering. A variant's placements overlay the rule's in placements.offering_ids_by_placement. See Experiments.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/offerings" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

Example 200 response:

JSON
{
  "current_offering_id": "default",
  "offerings": [
    {
      "description": "Standard plans",
      "identifier": "default",
      "metadata": null,
      "packages": [
        {
          "identifier": "$rc_monthly",
          "platform_product_identifier": "pro_monthly"
        },
        {
          "identifier": "$rc_annual",
          "platform_product_identifier": "pro_annual"
        },
        {
          "identifier": "$rc_lifetime",
          "platform_product_identifier": "pro_lifetime"
        }
      ]
    }
  ]
}

Get offerings without a user#

GET /v1/offerings · Auth: public app key or secret key

Same answer as the per-user call, without a customer override.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/offerings" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

Test Store product details#

GET /rcbilling/v1/subscribers/{app_user_id}/products · Auth: public app key

Product details the SDK needs for Test Store (and web) products, in the web billing products shape. Prices are 0 until the catalog stores Test Store prices.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Query parameters

Name Type Required Description
id array of string no Product ids; repeat the parameter. None lists every product of the app.

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/subscribers/user_1/products" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Product details.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "product_details": [
    {
      "identifier": "pro_monthly",
      "product_type": "subscription",
      "title": "Pro monthly",
      "description": null,
      "current_price": {
        "amount": 0,
        "amount_micros": 0,
        "currency": "USD"
      },
      "normal_period_duration": "P1M",
      "default_purchase_option_id": "base",
      "default_subscription_option_id": "base",
      "purchase_options": {
        "base": {
          "id": "base",
          "price_id": "base",
          "base": {
            "period_duration": "P1M",
            "cycle_count": 1,
            "price": {
              "amount": 0,
              "amount_micros": 0,
              "currency": "USD"
            }
          },
          "base_price": null,
          "trial": null,
          "intro_price": null
        }
      },
      "subscription_options": {
        "base": {
          "id": "base",
          "price_id": "base",
          "base": {
            "period_duration": "P1M",
            "cycle_count": 1,
            "price": {
              "amount": 0,
              "amount_micros": 0,
              "currency": "USD"
            }
          },
          "base_price": null,
          "trial": null,
          "intro_price": null
        }
      }
    }
  ]
}

Identity#

logIn and aliases.

Log in (identify)#

POST /v1/subscribers/identify · Auth: public app key or secret key

What Purchases.logIn() calls. When new_app_user_id is new and the current id is anonymous with no other ids, the anonymous customer takes the new id (201). When new_app_user_id exists, an anonymous-only current customer is merged into it (200). See Customers and app user IDs.

Request body (application/json)

Field Type Required Description
app_user_id string yes The current app user id.
new_app_user_id string yes Your user id.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/identify" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"$RCAnonymousID:abc123","new_app_user_id":"user_2"}'

Responses

  • 200: The user existed. Returns CustomerInfo.
  • 201: The user is new. Returns CustomerInfo.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}

Alias two app user ids#

POST /v1/subscribers/{app_user_id}/alias · Auth: public app key or secret key

Links new_app_user_id to the customer with the same merge rules as log in. The Android SDK uses it for Block Store recovery.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
new_app_user_id string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/alias" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Accepted.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

Attributes#

Customer attributes such as $email.

Read customer attributes#

GET /v1/subscribers/{app_user_id}/attributes · Auth: secret key · RevenueDot extension

The attributes the SDK or your server set, with when each was set. Needs a subscriber access token for this app user id (the app reads its own attributes after an Auth sign-in) or a secret key. An app's public key alone answers 401 with code 7224: attributes can hold an email address or a phone number. An unknown app user id answers an empty object.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/attributes" -H "Authorization: Bearer $SECRET_KEY"

Responses

  • 200: The attributes.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "subscriber_attributes": {
    "$displayName": {
      "value": "Ana",
      "updated_at_ms": 1790800914000
    },
    "plan_goal": {
      "value": "sleep",
      "updated_at_ms": 1790800914000
    }
  }
}

Set customer attributes#

POST /v1/subscribers/{app_user_id}/attributes · Auth: public app key or secret key

Saves attributes such as $email, $displayName or your own keys. A null value deletes the attribute, except attribution attributes ($mediaSource, $campaign, $adGroup, $ad, $keyword, $creative, $appleAds*, $claimType, $conversionType): they are write-once, so a stored value is kept and a different value or a null is ignored. Correct them with the REST API v2. An invalid $email is refused with 7263; the other attributes are saved. collectDeviceIdentifiers() sends $ip and $deviceVersion as "true": RevenueDot stores the request's IP address and the device and OS from the SDK's headers instead.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
attributes object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/attributes" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"attributes":{"$email":{"value":"ana@example.com","updated_at_ms":1790800914000}}}'

Responses

  • 200: Saved.
  • 400: Some attributes were not saved. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

SDK support#

Endpoints the SDK calls for features RevenueDot answers minimally, so the SDK keeps working.

Intro offer eligibility (StoreKit 1)#

POST /v1/subscribers/{app_user_id}/intro_eligibility · Auth: public app key or secret key

Answers null (unknown) for every product, so the SDK decides eligibility on the device.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
product_identifiers array of string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/intro_eligibility" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Eligibility per product.

Example 200 response:

JSON
{
  "pro_monthly": null
}

Sign a promotional offer (iOS)#

POST /v1/offers · Auth: public app key or secret key

What Purchases.promotionalOffer(forProductDiscount:product:) calls. RevenueDot signs each offer with the App Store app's In-App Purchase key (key_id, issuer_id and private_key in the app's credentials) the way Apple verifies it: ECDSA P-256 with SHA-256 over the bundle id, key id, product id, offer id, app account token, nonce and timestamp, DER-encoded and base64 (Apple's format). The app account token matches what the SDK puts on the payment: with StoreKit 2 the lowercase app user id when it is a UUID and empty otherwise; with StoreKit 1 the app user id. Without an In-App Purchase key the answer is 400 with code 7234, which the SDK reports as invalidAppleSubscriptionKeyError for that offer only.

Request body (application/json)

Field Type Required Description
app_user_id string yes
fetch_token string no The receipt or signed transaction; not needed for signing.
generate_offers array of object yes
generate_offers[].offer_id string yes Promotional offer id from App Store Connect.
generate_offers[].product_id string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/offers" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1","fetch_token":"…","generate_offers":[{"offer_id":"winback_50","product_id":"pro_monthly"}]}'

Responses

  • 200: One signature per offer.
  • 400: No In-App Purchase key, or no offers. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "offers": [
    {
      "key_id": "2X9R4HXF34",
      "offer_id": "winback_50",
      "product_id": "pro_monthly",
      "signature_data": {
        "nonce": "0f3c2a8e-5d7b-4d7e-9a53-3b8f2c1e6d40",
        "signature": "MEUCIQDD…",
        "timestamp": 1790800914034
      }
    }
  ]
}

Attribution data (deprecated iOS call)#

POST /v1/subscribers/{app_user_id}/attribution · Auth: public app key or secret key

What the deprecated Purchases.addAttributionData calls. The advertising identifiers in data (rc_idfa, rc_idfv, rc_gps_adid, rc_ip_address) become $idfa, $idfv, $gpsAdId and $ip. For Apple Search Ads (network 0) with iad-attribution true, the iAd fields become $mediaSource ("Apple Search Ads"), $campaign, $adGroup, $keyword, $creative and the $appleAds* ids. Attribution is write-once: a campaign attribute the customer already has is kept.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
network integer yes The SDK's AttributionNetwork: 0 Apple Search Ads.
data object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/attribution" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"network":0,"data":{"rc_idfv":"4CEE1BEE-3C19-4591-9E34-1AD968D7B609","Version3.1":{"iad-attribution":"true","iad-campaign-name":"Spring","iad-keyword":"scanner"}}}'

Responses

  • 200: Stored.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

Apple AdServices token#

POST /v1/subscribers/{app_user_id}/adservices_attribution · Auth: public app key or secret key

What enableAdServicesAttributionTokenCollection() sends once per install (the same token can also arrive as aad_attribution_token on a receipt). After answering, RevenueDot looks the token up with Apple's attribution API, retrying a 404 or 5xx 3 times 5 seconds apart, and stores an attributed install as $mediaSource ("Apple Search Ads"), $campaign, $adGroup, $keyword, $ad, $appleAdsCampaignId, $appleAdsAdGroupId, $appleAdsKeywordId, $appleAdsAdId, $appleAdsOrgId, $appleAdsCountryOrRegion, $claimType and $conversionType. Apple returns ids, not names. They show on the customer page and in every webhook's subscriber_attributes.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Request body (application/json)

Field Type Required Description
aad_attribution_token string yes The token from AAAttribution.attributionToken().

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/adservices_attribution" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"aad_attribution_token":"wD3Ma…"}'

Responses

  • 200: Accepted; the lookup runs after the answer.
  • 400: No token. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

SDK health report availability#

GET /v1/subscribers/{app_user_id}/health_report_availability · Auth: none

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/health_report_availability"

Responses

  • 200: No report logs.

Example 200 response:

JSON
{
  "report_logs": false
}

SDK health report#

GET /v1/subscribers/{app_user_id}/health_report · Auth: public app key

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/health_report" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Always passed.

Example 200 response:

JSON
{
  "status": "passed",
  "project_id": "proj18pzzkao",
  "app_id": "appvnrm0a5h",
  "checks": []
}

Product to entitlement mapping (offline entitlements)#

GET /v1/product_entitlement_mapping · Auth: public app key or secret key

Lets the SDK grant entitlements on the device while the server answers 5xx (offline entitlements). The SDK fetches it every 25 hours. A public key gets only its own app's products, keyed the way that SDK looks them up: App Store products by product id (product:monthly for a monthly billing plan), Google Play products by subscription:base_plan and by the bare subscription id, which carries every base plan's entitlements because Android purchases do not name their base plan. Consumables are left out. A secret key gets the whole project. See Offline entitlements.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/product_entitlement_mapping" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: The mapping.

Example 200 response:

JSON
{
  "product_entitlement_mapping": {
    "pro_monthly": {
      "product_identifier": "pro_monthly",
      "entitlements": [
        "pro"
      ]
    }
  }
}

Customer Center configuration#

GET /v1/customercenter/{app_user_id} · Auth: public app key

The project's Customer Center configuration: appearance, the management and no-active screens with their help paths, localized strings and the support email. A built-in default merged with what the project stored through POST /v2/projects/{project_id}/customer_center_config.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customercenter/user_1" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: The configuration.

Example 200 response:

JSON
{
  "customer_center": {
    "screens": {
      "MANAGEMENT": {
        "type": "MANAGEMENT",
        "title": "Manage subscription",
        "paths": []
      }
    },
    "support": {
      "email": "support@example.com"
    }
  }
}

Customer Center support ticket#

POST /v1/customercenter/support/create-ticket · Auth: public app key

Stores the ticket, emails the Customer Center support address (Reply-To the customer) and lists it under Lifecycle > Support. sent is false when ticket creation is off (support.support_tickets.allow_creation), a field is missing or invalid, or the customer sent more than 5 tickets in an hour; the SDK then offers its email link.

Request body (application/json)

Field Type Required Description
app_user_id string yes
customer_email string yes
issue_description string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customercenter/support/create-ticket" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1234","customer_email":"wren@example.com","issue_description":"My scans are not syncing."}'

Responses

  • 200: Whether the ticket was taken.

Example 200 response:

JSON
{
  "sent": true
}

Virtual currency balances#

GET /v1/subscribers/{app_user_id}/virtual_currencies · Auth: public app key

The customer's in-app currency balances by code. A customer the server has not seen has none.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/virtual_currencies" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Balances.

Example 200 response:

JSON
{
  "virtual_currencies": {
    "GLD": {
      "balance": 700,
      "name": "Gold",
      "code": "GLD",
      "description": null
    }
  }
}

Redeem a web purchase#

POST /v1/subscribers/redeem_purchase · Auth: public app key

What Purchases.redeemWebPurchase() calls with the redemption_token from a redemption link (<scheme>://redeem_web_purchase?redemption_token=rdrt_...). The anonymous customer who paid on RevenueDot's web checkout is merged into this app user id, like logIn: their purchases and attributes move over. The answer is the customer info, and PURCHASE_REDEEMED is sent once. See Redemption links.

  • 200: redeemed, or already redeemed by this customer (a retry is safe).
  • 400 · 7849: the token is unknown, malformed or from another project. The SDK returns invalidToken.
  • 400 · 7852: another customer redeemed it. The SDK returns purchaseBelongsToOtherUser.
  • 400 · 7853: the link expired (after the web config's redemption_link_hours, default 24). purchase_redemption_error_info.obfuscated_email says where a new link goes; it is emailed at most once an hour. The SDK returns expired with that email.

Headers

Name Type Required Description
X-Platform string no SDK platform (ios, android, macos, web ...). With a secret key it picks the project's app for that platform.

Request body (application/json)

Field Type Required Description
app_user_id string yes The app user id that gets the purchase.
redemption_token string yes From the redemption link.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/redeem_purchase" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1","redemption_token":"rdrt_q8Xc2kP0vN5mZ7tL1sW9hB3yR6dF4gJ2aE0uI8oK1nM"}'

Responses

  • 200: Customer info with the web purchase. Returns CustomerInfo.
  • 400: The token cannot be redeemed.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}

Register an Apple external purchase token (iOS)#

POST /v1/external_purchase_tokens · Auth: public app key

Part of Apple's external purchase and link-out flows, before a web checkout. The token is acknowledged with an id, which is all the SDK reads. The web checkout that follows is /rcbilling/v1/hosted-checkout.

Request body (application/json)

Field Type Required Description
app_user_id string yes
purchase_type IN_APP, LINK_OUT yes
token string no Apple's external purchase token, when there is one.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/external_purchase_tokens" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Registered.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "id": "ept3b1f0c9e2d8a4f6b9c7e5d3a1b2c4d6e",
  "purchase_type": "LINK_OUT",
  "is_sandbox": true,
  "token_source": "APPLE_SDK"
}

Rewarded ad verification status#

GET /v1/subscribers/{app_user_id}/ads/reward_verifications/{client_transaction_id} · Auth: public app key

What pollRewardVerification polls after the ad's reward callback fires (up to 10 times, about a second apart). The ad network's server-side callback (GET /v1/ads/admob/ssv) records the reward; the first matching reward rule grants it. See Ads: rewarded ads.

  • pending: no callback recorded yet for this id, or the grant is still being made. The SDK asks again.
  • verified: reward is the first granted reward and more_rewards the rest. reward is null when no rule matched: verified, nothing granted. The SDK then refreshes the customer's balances and customer info.
  • failed: failure_reason is user_mismatch (the reward belongs to another customer), missing_user (the callback had no user id) or grant_failed (the rule names a currency or entitlement that no longer exists).

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).
client_transaction_id string yes From generateRewardVerificationToken.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/ads/reward_verifications/$CLIENT_TRANSACTION_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: The verification status.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "status": "verified",
  "reward": {
    "type": "virtual_currency",
    "code": "GEMS",
    "amount": 10
  },
  "more_rewards": []
}

Amazon receipt details#

GET /v1/receipts/amazon/{store_user_id}/{receipt_id} · Auth: public app key

The Android SDK built for Amazon asks for it on subscription purchases and reads termSku, which it then posts as the product id. RevenueDot asks Amazon's Receipt Verification Service with the app's shared key and answers Amazon's receipt unchanged.

  • 400 · 7103: Amazon does not know the receipt or the user. 400 · 7662: the key is not an Amazon app's.
  • 500 · 7101: no shared key is saved, or Amazon rejected it. 503 · 7101: Amazon is unavailable; the SDK keeps the purchase unconsumed and retries. See Amazon Appstore setup.

Path parameters

Name Type Required Description
store_user_id string yes The Amazon user id.
receipt_id string yes Not encoded by the SDK; may contain /.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/receipts/amazon/$STORE_USER_ID/$RECEIPT_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Amazon's receipt.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.
  • 500: Server error. The SDK keeps the purchase and retries. Returns V1Error.
  • 503: The store could not be reached. Retry later. Returns V1Error.

Example 200 response:

JSON
{
  "autoRenewing": true,
  "betaProduct": false,
  "cancelDate": null,
  "cancelReason": null,
  "countryCode": "US",
  "freeTrialEndDate": null,
  "gracePeriodEndDate": null,
  "productId": "pro.subscription",
  "productType": "SUBSCRIPTION",
  "purchaseDate": 1790800914000,
  "receiptId": "q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM8jLz0kpLQ1JTSlFMsjILCoQ:3:11",
  "renewalDate": 1793392914000,
  "term": "1 Month",
  "termSku": "pro.monthly",
  "testTransaction": false
}

Paywall workflows (web SDK)#

GET /v1/subscribers/{app_user_id}/workflows · Auth: public app key

What purchases-js presentPaywall asks first. There are no workflows, so the SDK uses the offering's own paywall.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Query parameters

Name Type Required Description
type string no paywall.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/workflows" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: No workflows.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "workflows": [],
  "ui_config": {}
}

One paywall workflow (web SDK)#

GET /v1/subscribers/{app_user_id}/workflows/{workflow_id} · Auth: public app key

Never called, because the workflow list is empty.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).
workflow_id string yes

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/subscribers/user_1/workflows/$WORKFLOW_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 401: Unknown API key. Returns V1Error.
  • 404: No such workflow. Returns V1Error.

Restore eligibility (StoreKit 2)#

POST /v1/subscribers/{app_user_id}/restore/eligibility · Auth: public app key

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/restore/eligibility" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Always allowed.

Example 200 response:

JSON
{
  "is_purchase_allowed_by_restore_behavior": true
}

Remote config fallback (none)#

GET /v1/config/{domain} · Auth: public app key

The SDK's JSON fallback host path, never used with a proxy URL. Answers 204.

Path parameters

Name Type Required Description
domain string yes Config domain the SDK asks for (for example app).

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/config/app" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 204: No config.

Remote config: paywalls and UI settings#

POST /v1/config/{domain} · Auth: public app key

How current SDKs (iOS 5.83 and later) load paywalls. The body is an RC Container (application/x-rc-format): an 8-byte header ("RC", version 1), then elements of checksum (24 bytes, the first 24 bytes of SHA-256 of the payload), size (u32, little-endian), encoding (0, none) and 3 reserved bytes, then the payload padded to 8 bytes. Element 0 is the configuration JSON with the topics sources, ui_config (app, localizations, variable_config, custom_variables) and workflows (one per published paywall, keyed by workflow id, with offering_identifier). The other elements are the blobs those topics reference, inline. 204 when the manifest the SDK sent is current.

Path parameters

Name Type Required Description
domain string yes Config domain the SDK asks for (for example app).

Request body (application/json)

Field Type Required Description
manifest string no The manifest of the configuration the SDK holds.
prefetched_blobs array of string no Blob refs the SDK already holds; they are not inlined again.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/config/app" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"manifest":"v1.Qm9vdHN0cmFw","prefetched_blobs":[]}'

Responses

  • 200: RC Container.
  • 204: The SDK's configuration is current.

Download a remote-config blob#

GET /blobs/{blob_ref} · Auth: none

Public and immutable. The SDK downloads a blob here when it was not inline in the container (the sources topic points here).

Path parameters

Name Type Required Description
blob_ref string yes base64url of the first 24 bytes of SHA-256 of the blob.

Example request

Shell
curl -s "$REVENUEDOT_URL/blobs/$BLOB_REF"

Responses

  • 200: The blob (JSON).
  • 404: Unknown blob.

SDK paywall, Customer Center and ad events#

POST /v1/events · Auth: public app key

Stored for the paywall, ad and Customer Center charts and the Ads Overview (each SDK event id once). Ad events are the rc_ads_* types. A malformed batch is still answered 200 so the SDK does not resend it forever.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/events" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Accepted.

Example 200 response:

JSON
{}

SDK diagnostics (accepted, not stored)#

POST /v1/diagnostics · Auth: public app key

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/diagnostics" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Accepted.

Example 200 response:

JSON
{}

Web Billing#

Web checkout calls from the iOS SDK and purchases-js. The iOS SDK's paywall web checkout opens a Stripe Checkout on your own Stripe account (web billing). The purchases-js checkout for RevenueCat Billing (rcb_ keys, Stripe Elements inside the SDK) is not available and answers an error the SDK shows as a failed purchase.

Web offering products#

GET /rcbilling/v1/subscribers/{app_user_id}/offering_products · Auth: public app key

Defined in the iOS SDK with no caller. There are no web offerings.

Path parameters

Name Type Required Description
app_user_id string yes App user id, URL-encoded (anonymous ids look like $RCAnonymousID:...).

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/subscribers/user_1/offering_products" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: No web offerings.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "offerings": {}
}

Start a hosted web checkout#

POST /rcbilling/v1/hosted-checkout · Auth: public app key

The iOS SDK's paywall web checkout. RevenueDot finds the package in the offering, picks the project's Stripe app that sells it as a web product, and creates a Stripe Checkout Session for this app user id with that app's key. The SDK opens checkout_url and closes it when the browser reaches success_url or cancel_url; the success page records the purchase, so the customer info has it at once. See Sell on the web with Stripe.

  • 400 · 7000: the offering or package is unknown, or the package has no web product.
  • 503 · 7101: Stripe is unavailable or refused the key.

Request body (application/json)

Field Type Required Description
app_user_id string yes
package_id string yes Package lookup key, such as $rc_monthly.
presented_offering_identifier string yes The offering's lookup key.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/hosted-checkout" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1","package_id":"$rc_monthly","presented_offering_identifier":"web"}'

Responses

  • 200: The checkout.
  • 400: No web product for this package. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.
  • 503: The store could not be reached. Retry later. Returns V1Error.

Example 200 response:

JSON
{
  "operation_session_id": "wco_8k2m9q4x7a1b3c5d",
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4",
  "success_url": "https://api.revenuedot.app/pay/scanner/_/success",
  "cancel_url": "https://api.revenuedot.app/pay/scanner/_/cancel"
}

Web Billing purchase (not available)#

POST /rcbilling/v1/purchase · Auth: public app key

Defined in purchases-js with no caller. 400 with code 7000.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/purchase" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Prepare a Web Billing checkout (not available)#

POST /rcbilling/v1/checkout/prepare · Auth: public app key

purchases-js with an rcb_ key. 400 with code 7000: the purchase fails with an error in the SDK's purchase screen.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/checkout/prepare" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Start a Web Billing checkout (not available)#

POST /rcbilling/v1/checkout/start · Auth: public app key

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/checkout/start" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: Not available. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Web Billing checkout status#

GET /rcbilling/v1/checkout/{operation_session_id} · Auth: public app key

No checkout session exists: 400 with code 7877.

Path parameters

Name Type Required Description
operation_session_id string yes

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/checkout/$OPERATION_SESSION_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: No such session. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Refresh Web Billing checkout pricing#

PATCH /rcbilling/v1/checkout/{operation_session_id} · Auth: public app key

Path parameters

Name Type Required Description
operation_session_id string yes

Example request

Shell
curl -s -X PATCH "$REVENUEDOT_URL/rcbilling/v1/checkout/$OPERATION_SESSION_ID" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: No such session. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Complete a Web Billing checkout#

POST /rcbilling/v1/checkout/{operation_session_id}/complete · Auth: public app key

Path parameters

Name Type Required Description
operation_session_id string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/rcbilling/v1/checkout/$OPERATION_SESSION_ID/complete" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 400: No such session. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Web checkout branding#

GET /rcbilling/v1/branding · Auth: public app key

purchases-js with an rcb_ key loads it before a checkout: the app's name and the SDK's default look.

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/branding" -H "Authorization: Bearer $PUBLIC_KEY"

Responses

  • 200: Branding.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "id": "appvnrm0a5h",
  "app_name": "Scanner Web",
  "app_icon": null,
  "app_icon_webp": null,
  "app_wordmark": null,
  "app_wordmark_webp": null,
  "appearance": null,
  "support_email": null,
  "gateway_tax_collection_enabled": false,
  "brand_font_config": null
}

Subscriber tokens#

The SDK endpoints for one customer, authorized by a subscriber access token from POST /v2/projects/{project_id}/apps/{app_id}/authenticate instead of the app key. The RevenueCat SDKs call these paths in their internal token mode.

Get customer info (subscriber token)#

GET /v1/customer · Auth: none

The subscriber-token form of GET /v1/subscribers/{app_user_id}: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Headers

Name Type Required Description
X-Nonce string no Base64 nonce the SDK sends when entitlement verification is on; it is part of the signed message.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer"

Responses

  • 200: Customer info. Returns CustomerInfo.
  • 201: Customer info of a customer created by this call. Returns CustomerInfo.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}

Get offerings (subscriber token)#

GET /v1/customer/offerings · Auth: none

The subscriber-token form of GET /v1/subscribers/{app_user_id}/offerings: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer/offerings"

Responses

Example 200 response:

JSON
{
  "current_offering_id": "default",
  "offerings": [
    {
      "description": "Standard plans",
      "identifier": "default",
      "metadata": null,
      "packages": [
        {
          "identifier": "$rc_monthly",
          "platform_product_identifier": "pro_monthly"
        },
        {
          "identifier": "$rc_annual",
          "platform_product_identifier": "pro_annual"
        },
        {
          "identifier": "$rc_lifetime",
          "platform_product_identifier": "pro_lifetime"
        }
      ]
    }
  ]
}

Intro offer eligibility (StoreKit 1) (subscriber token)#

POST /v1/customer/intro_eligibility · Auth: none

The subscriber-token form of POST /v1/subscribers/{app_user_id}/intro_eligibility: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Request body (application/json)

Field Type Required Description
product_identifiers array of string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/intro_eligibility"

Responses

  • 200: Eligibility per product.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "pro_monthly": null
}

Attribution data (deprecated iOS call) (subscriber token)#

POST /v1/customer/attribution · Auth: none

The subscriber-token form of POST /v1/subscribers/{app_user_id}/attribution: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Request body (application/json)

Field Type Required Description
network integer yes The SDK's AttributionNetwork: 0 Apple Search Ads.
data object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/attribution" \
  -H "Content-Type: application/json" -d '{"network":0,"data":{"rc_idfv":"4CEE1BEE-3C19-4591-9E34-1AD968D7B609","Version3.1":{"iad-attribution":"true","iad-campaign-name":"Spring","iad-keyword":"scanner"}}}'

Responses

  • 200: Stored.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

Read customer attributes (subscriber token)#

GET /v1/customer/attributes · Auth: none · RevenueDot extension

The subscriber-token form of GET /v1/subscribers/{app_user_id}/attributes: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer/attributes"

Responses

  • 200: The attributes.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "subscriber_attributes": {
    "$displayName": {
      "value": "Ana",
      "updated_at_ms": 1790800914000
    },
    "plan_goal": {
      "value": "sleep",
      "updated_at_ms": 1790800914000
    }
  }
}

Set customer attributes (subscriber token)#

POST /v1/customer/attributes · Auth: none

The subscriber-token form of POST /v1/subscribers/{app_user_id}/attributes: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Request body (application/json)

Field Type Required Description
attributes object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/attributes" \
  -H "Content-Type: application/json" -d '{"attributes":{"$email":{"value":"ana@example.com","updated_at_ms":1790800914000}}}'

Responses

  • 200: Saved.
  • 400: Some attributes were not saved. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

Apple AdServices token (subscriber token)#

POST /v1/customer/adservices_attribution · Auth: none

The subscriber-token form of POST /v1/subscribers/{app_user_id}/adservices_attribution: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Request body (application/json)

Field Type Required Description
aad_attribution_token string yes The token from AAAttribution.attributionToken().

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/adservices_attribution" \
  -H "Content-Type: application/json" -d '{"aad_attribution_token":"wD3Ma…"}'

Responses

  • 200: Accepted; the lookup runs after the answer.
  • 400: No token. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{}

SDK health report (subscriber token)#

GET /v1/customer/health_report · Auth: none

The subscriber-token form of GET /v1/subscribers/{app_user_id}/health_report: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer/health_report"

Responses

  • 200: Always passed.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "status": "passed",
  "project_id": "proj18pzzkao",
  "app_id": "appvnrm0a5h",
  "checks": []
}

Customer Center configuration (subscriber token)#

GET /v1/customer/customercenter · Auth: none

The subscriber-token form of GET /v1/customercenter/{app_user_id}: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer/customercenter"

Responses

  • 200: The configuration.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "customer_center": {
    "screens": {
      "MANAGEMENT": {
        "type": "MANAGEMENT",
        "title": "Manage subscription",
        "paths": []
      }
    },
    "support": {
      "email": "support@example.com"
    }
  }
}

Customer Center support ticket (subscriber token)#

POST /v1/customer/customercenter/support/create-ticket · Auth: none

The subscriber-token form of POST /v1/customercenter/support/create-ticket: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Request body (application/json)

Field Type Required Description
app_user_id string yes
customer_email string yes
issue_description string yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/customercenter/support/create-ticket" \
  -H "Content-Type: application/json" -d '{"app_user_id":"user_1234","customer_email":"wren@example.com","issue_description":"My scans are not syncing."}'

Responses

  • 200: Whether the ticket was taken.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "sent": true
}

Virtual currency balances (subscriber token)#

GET /v1/customer/virtual_currencies · Auth: none

The subscriber-token form of GET /v1/subscribers/{app_user_id}/virtual_currencies: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer/virtual_currencies"

Responses

  • 200: Balances.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "virtual_currencies": {
    "GLD": {
      "balance": 700,
      "name": "Gold",
      "code": "GLD",
      "description": null
    }
  }
}

Restore eligibility (StoreKit 2) (subscriber token)#

POST /v1/customer/restore/eligibility · Auth: none

The subscriber-token form of POST /v1/subscribers/{app_user_id}/restore/eligibility: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/restore/eligibility"

Responses

  • 200: Always allowed.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "is_purchase_allowed_by_restore_behavior": true
}

Rewarded ad verification status (subscriber token)#

GET /v1/customer/ads/reward_verifications/{client_transaction_id} · Auth: none

The subscriber-token form of GET /v1/subscribers/{app_user_id}/ads/reward_verifications/{client_transaction_id}: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Path parameters

Name Type Required Description
client_transaction_id string yes From generateRewardVerificationToken.

Example request

Shell
curl -s "$REVENUEDOT_URL/v1/customer/ads/reward_verifications/$CLIENT_TRANSACTION_ID"

Responses

  • 200: The verification status.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "status": "verified",
  "reward": {
    "type": "virtual_currency",
    "code": "GEMS",
    "amount": 10
  },
  "more_rewards": []
}

Web offering products (subscriber token)#

GET /rcbilling/v1/customer/offering_products · Auth: none

The subscriber-token form of GET /rcbilling/v1/subscribers/{app_user_id}/offering_products: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/customer/offering_products"

Responses

  • 200: No web offerings.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "offerings": {}
}

Test Store product details (subscriber token)#

GET /rcbilling/v1/customer/products · Auth: none

The subscriber-token form of GET /rcbilling/v1/subscribers/{app_user_id}/products: same body and answer, for the app user id of the token. An app key, an expired token or another user's token answers 401 with code 7224.

Query parameters

Name Type Required Description
id array of string no Product ids; repeat the parameter. None lists every product of the app.

Example request

Shell
curl -s "$REVENUEDOT_URL/rcbilling/v1/customer/products"

Responses

  • 200: Product details.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "product_details": [
    {
      "identifier": "pro_monthly",
      "product_type": "subscription",
      "title": "Pro monthly",
      "description": null,
      "current_price": {
        "amount": 0,
        "amount_micros": 0,
        "currency": "USD"
      },
      "normal_period_duration": "P1M",
      "default_purchase_option_id": "base",
      "default_subscription_option_id": "base",
      "purchase_options": {
        "base": {
          "id": "base",
          "price_id": "base",
          "base": {
            "period_duration": "P1M",
            "cycle_count": 1,
            "price": {
              "amount": 0,
              "amount_micros": 0,
              "currency": "USD"
            }
          },
          "base_price": null,
          "trial": null,
          "intro_price": null
        }
      },
      "subscription_options": {
        "base": {
          "id": "base",
          "price_id": "base",
          "base": {
            "period_duration": "P1M",
            "cycle_count": 1,
            "price": {
              "amount": 0,
              "amount_micros": 0,
              "currency": "USD"
            }
          },
          "base_price": null,
          "trial": null,
          "intro_price": null
        }
      }
    }
  ]
}

Spend in-app currency as the subscriber#

POST /v1/customer/virtual_currencies/spend · Auth: none

Takes the amounts off the subscriber's balances, all or nothing. A balance cannot go below zero (422, code 7000); an unknown currency or a malformed body is 400 with code 7226. No webhook is sent, as for balance changes through the API.

Headers

Name Type Required Description
Idempotency-Key string no A retry with the same key spends nothing again.

Request body (application/json)

Field Type Required Description
adjustments object yes Amount to spend per currency code.
reference string or null no Your own note, kept in the ledger.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/customer/virtual_currencies/spend" \
  -H "Content-Type: application/json" -d '{"adjustments":{"GLD":5},"reference":"sword"}'

Responses

  • 200: The balances after the spend.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.
  • 422: The request is valid but cannot be done in this state. Returns V1Error.

Example 200 response:

JSON
{
  "virtual_currencies": {
    "GLD": {
      "balance": 95,
      "name": "Gold",
      "code": "GLD",
      "description": null
    }
  }
}

Auth sign-in#

Sign app users in with a Firebase or OpenID Connect ID token and get a subscriber access token for the /v1/customer/* paths, in the wire format of the RevenueCat SDKs' token login. See Auth.

Sign in with an identity provider's ID token#

POST /v1/auth/login · Auth: public app key · RevenueDot extension

For apps without their own backend (Auth): the app sends the ID token its identity provider gave it, with its public SDK key. RevenueDot verifies the token with the provider's published keys, maps it to an app user id (the first sign-in creates the link; later ones reuse it), merges an anonymous link_to_id like logIn, and returns tokens. The RevenueCat SDKs' token login sends the same body to POST /auth/login. CORS is open, for web apps.

Request body (application/json)

Field Type Required Description
method firebase, oidc, google, apple, facebook, anonymous yes firebase uses the project's Firebase provider; the others its OpenID Connect providers (the token's iss picks one). anonymous needs allow_anonymous.
scope string no The SDK sends openid offline_access.
id_token string no The provider's ID token. Not for anonymous.
link_to_id string no The app user id the app used before signing in. An anonymous id ($RCAnonymousID:…) merges into the signed-in user like logIn; any other id is ignored.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/auth/login" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"method":"firebase","scope":"openid offline_access","id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6…","link_to_id":"$RCAnonymousID:9f3c1a0e2b7d4c5a8e6f1b2d3c4a5e6f"}'

Responses

  • 200: Signed in.
  • 400: Malformed body (7226). Returns V1Error.
  • 401: The SDK key is unknown (7225) or the ID token fails verification (7224). Returns V1Error.
  • 403: Auth is off, or no enabled provider answers this method (7224). Returns V1Error.
  • 503: The provider's keys could not be loaded, or the server has no key to sign tokens with. Retry. Returns V1Error.

Example 200 response:

JSON
{
  "access_token": "eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjNmMmMxYTlkMGU4YjRjNzEifQ.eyJpc3MiOiJodHRwczovL2FwaS5yZXZlbnVlZG90LmFwcCIsInN1YiI6IlhrMmY5UXBMMGFaIiwicmMuYXBwX3VzZXJfaWQiOiJYazJmOVFwTDBhWiIsImFtciI6WyJmaXJlYmFzZSJdfQ.…",
  "refresh_token": "rdrf_9b0c…",
  "id_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.…",
  "scope": "openid offline_access",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Refresh the access token#

POST /v1/auth/token · Auth: public app key · RevenueDot extension

The refresh token is replaced: the old one stops working, and using it again answers 401 with 7224.

Request body (application/json)

Field Type Required Description
grant_type refresh_token yes
refresh_token string yes
scope string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/auth/token" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"grant_type":"refresh_token","refresh_token":"rdrf_9b0c…"}'

Responses

  • 200: New tokens.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "access_token": "eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjNmMmMxYTlkMGU4YjRjNzEifQ.eyJpc3MiOiJodHRwczovL2FwaS5yZXZlbnVlZG90LmFwcCIsInN1YiI6IlhrMmY5UXBMMGFaIiwicmMuYXBwX3VzZXJfaWQiOiJYazJmOVFwTDBhWiIsImFtciI6WyJmaXJlYmFzZSJdfQ.…",
  "refresh_token": "rdrf_9b0c…",
  "id_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.…",
  "scope": "openid offline_access",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Sign out#

POST /v1/auth/revoke · Auth: public app key · RevenueDot extension

A refresh token ends its session and every access token it issued; an access token (token_type_hint: access_token) stops working alone. An unknown token is not an error (RFC 7009).

Request body (application/json)

Field Type Required Description
token string yes
token_type_hint refresh_token, access_token no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/auth/revoke" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"token":"rdrf_9b0c…","token_type_hint":"refresh_token"}'

Responses

  • 200: Done.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Refresh the access token#

POST /auth/token · Auth: public app key · RevenueDot extension

The refresh token is replaced: the old one stops working, and using it again answers 401 with 7224.

Request body (application/json)

Field Type Required Description
grant_type refresh_token yes
refresh_token string yes
scope string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/auth/token" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"grant_type":"refresh_token","refresh_token":"rdrf_9b0c…"}'

Responses

  • 200: New tokens.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Example 200 response:

JSON
{
  "access_token": "eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjNmMmMxYTlkMGU4YjRjNzEifQ.eyJpc3MiOiJodHRwczovL2FwaS5yZXZlbnVlZG90LmFwcCIsInN1YiI6IlhrMmY5UXBMMGFaIiwicmMuYXBwX3VzZXJfaWQiOiJYazJmOVFwTDBhWiIsImFtciI6WyJmaXJlYmFzZSJdfQ.…",
  "refresh_token": "rdrf_9b0c…",
  "id_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.…",
  "scope": "openid offline_access",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Sign out#

POST /auth/revoke · Auth: public app key · RevenueDot extension

A refresh token ends its session and every access token it issued; an access token (token_type_hint: access_token) stops working alone. An unknown token is not an error (RFC 7009).

Request body (application/json)

Field Type Required Description
token string yes
token_type_hint refresh_token, access_token no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/auth/revoke" -H "Authorization: Bearer $PUBLIC_KEY" \
  -H "Content-Type: application/json" -d '{"token":"rdrf_9b0c…","token_type_hint":"refresh_token"}'

Responses

  • 200: Done.
  • 400: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns V1Error.
  • 401: Unknown API key. Returns V1Error.

Public key of RevenueDot's ID and access tokens#

GET /.well-known/jwks.json · Auth: none · RevenueDot extension

One Ed25519 key (OKP), derived from the server's signing key. Backends verify an Auth id_token with it (match kid, check iss and aud). Empty keys when the server has no signing or encryption key.

Example request

Shell
curl -s "$REVENUEDOT_URL/.well-known/jwks.json"

Responses

  • 200: The key set.

Example 200 response:

JSON
{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "gXdn2hmqR_TbdtQwK02laE0YgFz0Rtf918LICLrgZhg",
      "kid": "3f2c1a9d0e8b4c71",
      "alg": "EdDSA",
      "use": "sig"
    }
  ]
}

Store notifications#

Where App Store Connect and Google Pub/Sub send server notifications.

App Store Server Notifications v2#

POST /v1/notifications/apple/{app_id} · Auth: none

Set this URL (shown on the app's page in the dashboard) as the Production and Sandbox Server URL in App Store Connect, with version 2 notifications. RevenueDot verifies Apple's signature and the bundle id, stores the raw body, copies it to notification_forward_url when set, and applies it.

  • 200: handled, including notifications about purchases this server has not seen (stored; applied only with track_new_purchases).
  • 400: the payload cannot be verified or belongs to another app. App Store Connect shows it as failed.
  • 404: no App Store app with this id.
  • 500: RevenueDot failed; Apple retries.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (application/json)

Field Type Required Description
signedPayload string yes Apple's signed JWS.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/apple/$APP_ID"

Responses

  • 200: Handled.
  • 400: Unverifiable.
  • 404: Unknown app.
  • 500: Failed; Apple retries.

Example 200 response:

JSON
{
  "ok": true
}

Google Play real-time developer notifications (Pub/Sub push)#

POST /v1/notifications/google/{app_id} · Auth: Pub/Sub push token (optional)

Set this URL as the endpoint of a Pub/Sub push subscription on the topic Google Play publishes to. When the app's pubsub_audience credential is set, the push must carry a Google-signed OIDC token for that audience (and for pubsub_service_account when set). Each message is stored once (by message id), forwarded when notification_forward_url is set, and applied by reading the purchase from the Play Developer API.

  • 200: handled, a duplicate, ignored (another package, not a developer notification) or an invalid token that can never succeed.
  • 400: not a Pub/Sub push body. 401: bad push token. 404: no Google Play app with this id.
  • 500 or 503: a temporary failure; Pub/Sub redelivers.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (application/json)

Field Type Required Description
message object yes
message.data string no Base64 JSON developer notification.
message.messageId string no
message.publishTime string no
subscription string no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/google/$APP_ID"

Responses

  • 200: Handled.
  • 400: Not a push body. Returns V1Error.
  • 401: Bad push token. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure; Pub/Sub retries. Returns V1Error.
  • 503: Google's signing keys could not be loaded. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed"
}

Amazon Appstore Real-time Notifications (SNS)#

POST /v1/notifications/amazon/{app_id} · Auth: none

Add this URL as an endpoint under App Services → Real-time Notifications in the Amazon Appstore Console. Amazon delivers through Amazon SNS. Every message must carry a valid SNS signature (SignatureVersion 1 or 2, certificate from an sns.<region>.amazonaws.com URL). The subscription confirmation is accepted by fetching its SubscribeURL, which is what makes Amazon show "Verified". Each message is stored once (by SNS message id), forwarded when notification_forward_url is set, and applied by reading the receipt from Amazon's Receipt Verification Service.

  • 200: handled, confirmed, a duplicate, ignored (another package) or a receipt that can never be checked.
  • 400: not an SNS message, a bad signature, or a topic other than the app's sns_topic_arn. 404: no Amazon app with this id.
  • 500 or 503: a temporary failure; SNS retries. See Amazon Appstore setup.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (application/json)

Field Type Required Description
Type Notification, SubscriptionConfirmation, UnsubscribeConfirmation yes
MessageId string yes
TopicArn string no
Message string no For notifications: Amazon's JSON (appPackageName, notificationType, appUserId, receiptId, relatedReceipts, timestamp, betaProductTransaction).
SubscribeURL string no Subscription confirmations only.

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/amazon/$APP_ID"

Responses

  • 200: Handled.
  • 400: Not accepted. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure; SNS retries. Returns V1Error.
  • 503: The SNS certificate or confirmation failed; SNS retries. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed"
}

Stripe webhooks#

POST /v1/notifications/stripe/{app_id} · Auth: none

Add this URL as a webhook endpoint in your Stripe account with the events customer.subscription.created, .updated, .deleted, .paused, .resumed, invoice.paid, invoice.payment_failed, invoice.updated, charge.refunded and checkout.session.completed, and save its signing secret on the app. Each event is stored once (by event id), forwarded when notification_forward_url is set, and applied by reading the subscription from Stripe, so event order does not matter. Other event types are accepted and ignored.

  • 200: handled, a duplicate, ignored, an unknown purchase (applied only with track_new_purchases) or an object Stripe no longer has.
  • 400: no signing secret saved, a missing or wrong Stripe-Signature, or not a Stripe event. 404: no Stripe app with this id.
  • 500: a temporary failure; Stripe retries for three days. See Stripe setup.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Headers

Name Type Required Description
Stripe-Signature string yes t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">, checked with the app's stripe_webhook_secret within 5 minutes.

Request body (application/json)

Field Type Required Description
id string yes evt_…
object "event" no
type string yes
created integer no
livemode boolean no
data object yes
data.object object no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/stripe/$APP_ID"

Responses

  • 200: Handled.
  • 400: Not accepted. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure; Stripe retries. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed"
}

Paddle notifications#

POST /v1/notifications/paddle/{app_id} · Auth: none

A notification destination in your Paddle account (Apply in Paddle creates it: POST /v2/projects/{project_id}/apps/{app_id}/actions/apply_notification_settings) with the subscription, transaction and adjustment events. Each event is stored once (by event_id), forwarded when notification_forward_url is set, and applied by reading the subscription or transaction from Paddle, so delivery order does not matter. Approved full refunds and chargebacks (adjustment.created, adjustment.updated) mark the paid period refunded. Simulated events (ntfsimevt_…) count as received and change nothing. Paddle counts only 200 as delivered.

  • 200: handled, a duplicate, simulated, ignored, an unknown purchase (applied only with track_new_purchases) or an object Paddle no longer has.
  • 400: no secret key saved, a missing or wrong Paddle-Signature, or not a Paddle event. 404: no Paddle app with this id.
  • 500: a temporary failure; Paddle retries. See Paddle setup.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Headers

Name Type Required Description
Paddle-Signature string yes ts=<unix seconds>;h1=<hex HMAC-SHA256 of "<ts>:<body>">, checked with the destination's secret key (paddle_webhook_secret) within 5 minutes. Several h1 values are accepted while a key rotates.

Request body (application/json)

Field Type Required Description
event_id string yes evt_…
event_type string yes
occurred_at string no
notification_id string no ntf_…
data object yes

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/paddle/$APP_ID"

Responses

  • 200: Handled.
  • 400: Not accepted. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure; Paddle retries. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed"
}

Roku Pay push notifications#

POST /v1/notifications/roku/{app_id} · Auth: none

Set this URL as the push notification URL under Roku Pay web services in the Roku developer dashboard. Roku has one URL per developer account: a push for another channel goes to the project's Roku app with that roku_channel_id. The body is a JWS (RS256) signed with a key from Roku's published key set (the test key set for Roku's test endpoint), with issuer Roku, Inc. urn:roku:apps:partner-service.roku.com and x-Roku-message-type: roku.rpay.push; the message is base64 JSON in x-Roku-message. Each push is stored once (by x-Roku-message-key), forwarded when notification_forward_url is set, and applied by validating its transaction with Roku again.

  • 200: handled; the body is the push's responseKey.
  • 400: not a signed Roku push (bad signature, unknown key, another issuer or message type). 404: no Roku app with this id.
  • 500: a temporary failure; Roku retries for 36 hours. See Roku setup.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (text/plain)

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/roku/$APP_ID"

Responses

  • 200: Handled: the push's responseKey.
  • 400: Not accepted. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure; Roku retries. Returns V1Error.

Galaxy Store server notifications#

POST /v1/notifications/galaxy/{app_id} · Auth: none

Set this URL as the Instant Server Notification URL in Samsung Seller Portal. The body is a JWT (RS256) from iap.samsungapps.com whose aud names the app's package. With the app's galaxy_iap_public_key saved its signature must match; without it nothing in the body is trusted. Either way the purchase it names is read again from Samsung (receipt and subscription APIs) and applied. TEST counts as received.

  • 200: handled, a duplicate, ignored or an unknown purchase (applied only with track_new_purchases). verified says whether the signature was checked.
  • 400: not a Samsung notification, a signature that does not match, or another package. 404: no Galaxy Store app with this id.
  • 500: a temporary failure. See Galaxy Store setup.

Path parameters

Name Type Required Description
app_id string yes App id (app...).

Request body (text/plain)

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/galaxy/$APP_ID"

Responses

  • 200: Handled.
  • 400: Not accepted. Returns V1Error.
  • 404: Unknown app. Returns V1Error.
  • 500: Temporary failure. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed",
  "verified": true
}

Stripe Connect webhooks (RevenueDot's platform)#

POST /v1/notifications/stripe-connect · Auth: none · RevenueDot extension

The platform's endpoint for "Events on Connected accounts". Each event carries account and is handled for every app connected to that account exactly like its own Stripe endpoint (stored once per app, forwarded, applied), when its livemode matches the connection's mode. account.updated refreshes onboarding status; account.application.deauthorized disconnects the apps.

  • 200: handled, a duplicate, unknown_account (no app is connected to it), other_mode, or a platform event without account. 400: a bad signature or not a Stripe event. 404: Connect is not set up on this server. 500: a temporary failure; Stripe retries.

Headers

Name Type Required Description
Stripe-Signature string yes Checked against each of the platform's webhook secrets (REVENUEDOT_STRIPE_CONNECT_WEBHOOK_SECRET).

Request body (application/json)

Field Type Required Description
id string yes evt_…
type string yes
account string no acct_…
livemode boolean no
data object yes
data.object object no

Example request

Shell
curl -s -X POST "$REVENUEDOT_URL/v1/notifications/stripe-connect"

Responses

  • 200: Handled.
  • 400: Not accepted. Returns V1Error.
  • 404: Connect is not set up. Returns V1Error.
  • 500: Temporary failure; Stripe retries. Returns V1Error.

Example 200 response:

JSON
{
  "status": "processed"
}

Response signing#

Trusted Entitlements: the public key responses are signed with.

Public key for response signatures#

GET /.well-known/revenuedot-signing-key · Auth: none

The Ed25519 root public key this server signs SDK responses with (Trusted Entitlements). Pin this key in SDK builds that verify responses. The server signs only when REVENUEDOT_SIGNING_KEY is set; otherwise this answers 404. See Trusted Entitlements.

Example request

Shell
curl -s "$REVENUEDOT_URL/.well-known/revenuedot-signing-key"

Responses

  • 200: The key.
  • 404: Signing is off. Returns V1Error.

Example 200 response:

JSON
{
  "algorithm": "Ed25519",
  "public_key": "ZzwPxGlon0E8ErpDh9QAH0Jh6+E6D6qufvTSetXZY9Y=",
  "encoding": "base64",
  "header": "X-Signature",
  "docs": "https://revenuedot.app/docs"
}

Objects#

The shapes the operations above send and return.

CustomerInfo#

Field Type Required Description
request_date string yes Server time of the response. ISO 8601 in UTC, whole seconds.
request_date_ms integer yes Server time of the response. Epoch milliseconds.
subscriber object yes
subscriber.entitlements object yes Entitlements the customer has now or had, keyed by lookup key. Check expires_date (or use the SDK's isActive).
subscriber.first_seen string yes When the customer was first seen. ISO 8601 in UTC, whole seconds.
subscriber.last_seen string yes When the customer was last seen. ISO 8601 in UTC, whole seconds.
subscriber.management_url string or null no Always null today.
subscriber.non_subscriptions object yes One-time purchases by product id, oldest first.
subscriber.original_app_user_id string yes The customer's first app user id.
subscriber.original_application_version string or null no Always null today.
subscriber.original_purchase_date string or null no Earliest purchase. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
subscriber.other_purchases object no Always empty.
subscriber.subscriber_attributes object no Only in answers to secret-key requests.
subscriber.subscriptions object yes Latest subscription per product id.

EntitlementInfo#

Field Type Required Description
expires_date string or null yes When access ends. Null for lifetime access. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
grace_period_expires_date string or null yes End of the billing grace period, when the store grants one. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
product_identifier string yes Store product id that gives this access.
product_plan_identifier string no Google Play base plan id, only when there is one.
purchase_date string yes Start of the current period. ISO 8601 in UTC, whole seconds.

NonSubscriptionInfo#

Field Type Required Description
display_name string or null no
id string yes RevenueDot purchase id.
is_sandbox boolean yes
original_purchase_date string or null no Purchase time. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
purchase_date string or null yes Purchase time. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
store string yes
store_transaction_id string yes Store transaction id.
price object no
price.amount number yes Price in the purchase currency.
price.currency string yes ISO 4217 currency code.

Offerings#

Field Type Required Description
current_offering_id string or null yes Lookup key of the customer's current offering: their override, else their experiment variant's offering, else the first live targeting rule's, else the project's current offering.
offerings array of object yes
offerings[].description string yes Offering display name.
offerings[].identifier string yes Offering lookup key.
offerings[].metadata object or null yes Your JSON metadata.
offerings[].packages array of object yes
offerings[].packages[].identifier string yes Package lookup key, for example $rc_monthly.
offerings[].packages[].platform_product_identifier string yes Store product id for the calling app.
offerings[].packages[].platform_product_plan_identifier string no Google Play base plan id, when the product is subscription:base-plan.
placements object no What currentOffering(forPlacement:) reads.
placements.fallback_offering_id string or null no Lookup key of the offering for placements not listed below: the current offering.
placements.offering_ids_by_placement object no Placement id → offering lookup key, or null for no paywall there: the matching targeting rule's placements, overlaid with the customer's experiment variant's.
targeting object no Present when a targeting rule matched the customer.
targeting.revision integer yes The rule's revision.
targeting.rule_id string yes The targeting rule that matched.

ReceiptResponse#

Field Type Required Description
request_date string yes Server time of the response. ISO 8601 in UTC, whole seconds.
request_date_ms integer yes Server time of the response. Epoch milliseconds.
subscriber object yes
subscriber.entitlements object yes Entitlements the customer has now or had, keyed by lookup key. Check expires_date (or use the SDK's isActive).
subscriber.first_seen string yes When the customer was first seen. ISO 8601 in UTC, whole seconds.
subscriber.last_seen string yes When the customer was last seen. ISO 8601 in UTC, whole seconds.
subscriber.management_url string or null no Always null today.
subscriber.non_subscriptions object yes One-time purchases by product id, oldest first.
subscriber.original_app_user_id string yes The customer's first app user id.
subscriber.original_application_version string or null no Always null today.
subscriber.original_purchase_date string or null no Earliest purchase. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
subscriber.other_purchases object no Always empty.
subscriber.subscriber_attributes object no Only in answers to secret-key requests.
subscriber.subscriptions object yes Latest subscription per product id.
purchased_products object no One entry per purchase the receipt contained.

SubscriptionInfo#

Field Type Required Description
auto_resume_date string or null no When a paused Google Play subscription resumes. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
billing_issues_detected_at string or null no When the latest renewal failed. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
display_name string or null no Product display name.
expires_date string or null yes End of the current period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
grace_period_expires_date string or null no End of the billing grace period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
is_sandbox boolean yes True for sandbox and Test Store purchases.
management_url string or null no Always null today.
original_purchase_date string or null no Start of the subscription. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
ownership_type PURCHASED, FAMILY_SHARED no
period_type normal, trial, intro, promotional, prepaid yes
purchase_date string or null yes Start of the current period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
refunded_at string or null no When the store refunded it. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
store app_store, mac_app_store, play_store, amazon, stripe, rc_billing, promotional, test_store, paddle, roku, external yes
store_transaction_id string or null no Latest store transaction id (Apple), order id (Google) or Test Store token.
unsubscribe_detected_at string or null no When auto-renew was turned off. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null.
product_plan_identifier string no Google Play base plan id, only when there is one.
price object no
price.amount number yes Price in the purchase currency.
price.currency string yes ISO 4217 currency code.

V1Error#

Field Type Required Description
code integer yes RevenueCat-compatible backend error code. See the error table.
message string yes What went wrong.
attribute_errors array of object no Only for 7263.
attribute_errors[].key_name string no
attribute_errors[].message string no