Use this page with AI

Copy this into your coding assistant and add your request.

Read https://openiap.dev/docs/apis/verify-app-ownership and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.

My request: [describe what customers should be able to do]
See an example request →

verifyAppOwnership

Check ownership of the app download separately from in-app purchases. Use this at launch for a paid app; keep verifyPurchase for products bought inside the app.

Available in Client Protocol 1.0.1, openiap-apple 4.0.1, and openiap-google 4.0.1. Rebuild your native app after upgrading its SDK.

Follow your framework setup, including initConnection when required for provider setup. Godot on Android requires a successful connection. Native StoreKit and Play Integrity checks do not need a product catalog. Google Play requires a linked Cloud project with Play Integrity enabled.

One app-level verification flow#

All SDKs expose the same operation and generated result contract. Your app calls verifyAppOwnership once, then handles licensed, unlicensed, or unknown. The native adapter owns StoreKit and Play Integrity calls; your UI does not branch on the operating system.

For server-controlled access, send the complete result to your authenticated backend on either platform. It verifies signedAppTransactionIOS on Apple or decodes integrityTokenAndroid on Play, then returns the same status vocabulary. A collected Play token remains unknown until this step succeeds. For device-only Apple access, the verified local status is available immediately.

Platform behavior#

PlatformResult
App Store: iOS 16+, macOS 14+, tvOS 16+, watchOS 9+, visionOSStoreKit verifies AppTransaction. A verified acquisition returns licensed; a reported revocation returns unlicensed. An unverified transaction returns unknown with verification-failed.
Google PlayCollects a standard Play Integrity token. Returns unknown with backend-verification-required until your backend decodes the token and decides whether access is allowed.
Older Apple runtimes, Amazon, Horizon, VegaOS, Expo's Onside integrationReturns unknown with unsupported. Community providers must implement the capability; a provider without a handler rejects with feature-not-supported.

Local Apple verdicts require an Xcode 27 build to check revocation. Earlier builds return unknown with backend-verification-required and signed evidence. Your backend can check revocation. Local checks alone cannot provide complete piracy protection.

Signature#

swift
func verifyAppOwnership(_ options: VerifyAppOwnershipProps? = nil) async throws -> AppOwnershipVerificationResult

Custom Dart classes using implements QueryResolver must add this method. Classes extending QueryResolver inherit an unsupported default; FlutterInappPurchase already implements the capability.

Parameters#

The optional VerifyAppOwnershipProps contains google, required for Google Play and ignored on Apple platforms.

PlayIntegrityParamsAndroid fieldValue
cloudProjectNumberPositive decimal string identifying the app's Google Cloud project. Use the project number, not the project ID or a secret key.
requestHashNonempty digest of a backend challenge and the request being protected. At most 500 UTF-8 bytes. Hash sensitive inputs; do not include them as plaintext.

Each call prepares a token provider and requests one token. Avoid polling: Google limits preparations per app instance and applies project quotas. Preparation and token collection have a combined 60-second timeout.

Returns#

AppOwnershipVerificationResult preserves the store evidence:

FieldMeaning
statuslicensed, unlicensed, or unknown. Unknown is an inconclusive check, never a denial of ownership.
storeIdStore producing the result, such as apple or play.
reasonFor unknown: backend-verification-required, verification-failed, network-error, service-error, or unsupported.
appTransactionIOSVerified AppTransaction metadata on Apple platforms.
signedAppTransactionIOSSigned app transaction JWS for backend verification. This is separate from an in-app purchase JWS.
integrityTokenAndroidEncrypted Play Integrity token. Send it to your backend without decoding or trusting it in the app.

Invalid Google parameters reject with developer-error. Cancellation propagates. Service outages and throttling return unknown with service-error. Godot emits purchase_error and returns null for invalid input or bridge failures.

Try the example apps#

The two native examples and all six framework examples expose App ownership in their purchase or Store screen. Select Check app ownership to see the status, store, reason, and evidence lengths. For Google Play, enter the linked Cloud project number and a request hash from your backend. Leave both fields empty on Apple; unsupported stores return unknown with unsupported. These free test apps verify API wiring; they do not verify paid-app acquisition or refund outcomes.

Launch example#

Generate requestDigest from your backend challenge. verifyOwnershipOnBackend and applyAppAccessPolicy are functions you implement in your app.

ts
import {initConnection, verifyAppOwnership} from 'expo-iap';
// The same API is exported from react-native-iap.
if (!(await initConnection())) throw new Error("Store connection unavailable");

// Your backend issues a fresh challenge bound to the signed-in session.
const google = {cloudProjectNumber: '123456789012', requestHash: requestDigest};
const evidence = await verifyAppOwnership({google});
// One backend entry point verifies the platform's evidence and request binding.
const decision = await verifyOwnershipOnBackend(evidence);
applyAppAccessPolicy(decision.status); // licensed | unlicensed | unknown

Backend verification#

Google Play: authenticate the app's backend request, then call Google's decodeIntegrityToken API using server credentials. Verify requestPackageName, timestampMillis, and requestHash against the expected app and the original request. Bind the challenge to the session and reject reuse. Never put Google credentials in a shipped app.

After those checks, map appLicensingVerdict LICENSED to licensed, UNLICENSED to unlicensed, and UNEVALUATED or a missing verdict to unknown. Also require your expected package, signing certificate, version policy, and PLAY_RECOGNIZED app integrity verdict. Device integrity is a separate policy decision; a licensing verdict alone does not establish app or device integrity. A failed identity or request-binding check must not grant access even when the decoded licensing field says LICENSED.

Apple: verify signedAppTransactionIOS using Apple's signed-data verifier and trusted roots. Check the bundle ID, App Store app ID, environment, and revocation metadata before granting backend access. Reject test environments for production entitlements. Use the app's device-verification fields when enforcing device binding.

Return your decision through the developer's authenticated backend. This API collects evidence and performs the StoreKit local check; it does not call IAPKit or create a hosted licensing endpoint.

Standard Play Integrity requests · Play Integrity verdicts · Apple AppTransaction

Testing app ownership#

A free app can exercise the API and acquisition evidence, but licensed does not prove a price was paid. Sandbox, Xcode, and fixture results verify test behavior rather than a production paid-app purchase.

To validate paid-app access, use a paid store listing and test accounts with and without an entitlement. Check purchase and refund or revocation outcomes where the store supports them. Google Play also needs the linked Cloud project and your backend token decoder.

Offline behavior#

StoreKit may verify cached app evidence offline. Play needs connectivity to collect and decode a fresh token. Network failures return unknown with network-error, not unlicensed. The SDK does not persist a license decision. Your app owns any grace period based on a previous server-verified decision, its expiry, and the retry policy. Do not repeatedly request tokens or permanently lock out an owner after a transient failure.