Skip to main content
Version: 3.3 (Current)

iOS Specific APIs

The following iOS‑only helpers expose StoreKit and App Store specific capabilities. Most day‑to‑day flows are covered by the cross‑platform Unified APIs; use these only when you need iOS features.

Alternative Billing (iOS 16.0+)

Transaction Management

clearTransactionIOS()

Clears all pending transactions from the iOS payment queue. Useful if your app previously crashed or missed finishing transactions.

import {clearTransactionIOS, getPendingTransactionsIOS} from 'expo-iap';

// Inspect then clear
const pending = await getPendingTransactionsIOS();
if (pending.length) {
await clearTransactionIOS();
}

Returns: Promise<void>

getPromotedProductIOS()

Gets the currently promoted product, if any. Requires iOS 11+.

import {getPromotedProductIOS} from 'expo-iap';

const promoted = await getPromotedProductIOS();
if (promoted) {
// Show your purchase UI for the promoted product
}

Returns: Promise<Product | null>

requestPurchaseOnPromotedProductIOS()

Initiates the purchase flow for the currently promoted product. Requires iOS 11+.

import {requestPurchaseOnPromotedProductIOS} from 'expo-iap';

await requestPurchaseOnPromotedProductIOS();
// Purchase result is delivered via purchase listeners/useIAP callbacks

Returns: Promise<void>

getPendingTransactionsIOS()

Returns all transactions that are pending completion in the StoreKit payment queue.

import {getPendingTransactionsIOS} from 'expo-iap';

const pending = await getPendingTransactionsIOS();

Returns: Promise<Purchase[]>

isEligibleForIntroOfferIOS()

Checks if the user is eligible for an introductory offer for a subscription group. Requires iOS 12.2+.

import {isEligibleForIntroOfferIOS, fetchProducts} from 'expo-iap';

// Example: derive group ID from a fetched subscription product
const [sub] = await fetchProducts({skus: ['your_sub_sku'], type: 'subs'});
const groupId = sub?.subscriptionInfoIOS?.subscriptionGroupId ?? '';
const eligible = groupId ? await isEligibleForIntroOfferIOS(groupId) : false;

Returns: Promise<boolean>

subscriptionStatusIOS()

Returns detailed subscription status information using StoreKit 2. Requires iOS 15+.

import {subscriptionStatusIOS} from 'expo-iap';

const statuses = await subscriptionStatusIOS('your_sub_sku');

Returns: Promise<SubscriptionStatusIOS[]>

currentEntitlementIOS()

Returns the current entitlement for a given SKU using StoreKit 2. Requires iOS 15+.

import {currentEntitlementIOS} from 'expo-iap';

const entitlement = await currentEntitlementIOS('your_sub_or_product_sku');

Returns: Promise<Purchase | null>

latestTransactionIOS()

Returns the most recent transaction for a given SKU using StoreKit 2. Requires iOS 15+.

import {latestTransactionIOS} from 'expo-iap';

const last = await latestTransactionIOS('your_sku');

Returns: Promise<Purchase | null>

showManageSubscriptionsIOS()

Opens the native subscription management interface and returns purchases for subscriptions whose auto‑renewal status changed while the sheet was open. Requires iOS 15+.

import {showManageSubscriptionsIOS} from 'expo-iap';

const changed = await showManageSubscriptionsIOS();
if (changed.length > 0) {
// Update your UI / server using returned purchases
}

Returns: Promise<Purchase[]>

beginRefundRequestIOS()

Presents the refund request sheet for a specific SKU. Requires iOS 15+.

import {beginRefundRequestIOS} from 'expo-iap';

const status = await beginRefundRequestIOS('your_sku');
// status: 'success' | 'userCancelled'

Returns: Promise<'success' | 'userCancelled'>

isTransactionVerifiedIOS()

Verifies the latest transaction for a given SKU using StoreKit 2. Requires iOS 15+.

import {isTransactionVerifiedIOS} from 'expo-iap';

const ok = await isTransactionVerifiedIOS('your_sku');

Returns: Promise<boolean>

getTransactionJwsIOS()

Returns the JSON Web Signature (JWS) for a transaction derived from a given SKU. Use this for server‑side validation. Requires iOS 15+.

import {getTransactionJwsIOS} from 'expo-iap';

const jws = await getTransactionJwsIOS('your_sku');

Returns: Promise<string>

getReceiptDataIOS()

Returns the base64‑encoded receipt data for server validation.

import {getReceiptDataIOS} from 'expo-iap';

const receipt = await getReceiptDataIOS();

Returns: Promise<string>

syncIOS()

Forces a sync with StoreKit to ensure all transactions are up to date. Requires iOS 15+.

import {syncIOS} from 'expo-iap';

await syncIOS();

Returns: Promise<void>

presentCodeRedemptionSheetIOS()

Presents the system sheet for redeeming App Store promo/offer codes.

import {presentCodeRedemptionSheetIOS} from 'expo-iap';

await presentCodeRedemptionSheetIOS();

Returns: Promise<boolean>

getAppTransactionIOS()

Gets app transaction information for iOS apps (iOS 16.0+). AppTransaction represents the initial purchase that unlocked the app - Apple's way of proving the app was legitimately downloaded from the App Store.

Runtime: iOS 16.0+; Build: Xcode 15.0+ with iOS 16.0 SDK. Older SDKs will throw.

Important

AppTransaction is not for payment verification. It is a trust/authenticity layer. For payment verification, use Transaction verification or server-side receipt validation.

When to Use AppTransaction

Most apps do not need this. Use only when:

ScenarioWhy It Helps
Free app with IAP + cracking concernsVerifies app was installed via App Store, not side-loaded
Enterprise/Compliance requirementsApple-signed proof of app ownership for audits
Anti-fraud signal in high-value appsAdditional trust signal for fraud detection
Games with cheating concernsHelps identify modified/unofficial builds

Do NOT use for:

  • Paid apps (purchase already verified at download)
  • Standard subscription apps (Transaction verification is sufficient)
  • "Better payment verification" (AppTransaction has no payment data)

Basic Usage

import {getAppTransactionIOS} from 'expo-iap';
import {Platform} from 'react-native';

const verifyAppAuthenticity = async () => {
if (Platform.OS !== 'ios') return null;

try {
const appTransaction = await getAppTransactionIOS();

if (!appTransaction) {
// Unverified - could be sandbox, TestFlight, or tampered
console.warn('App transaction could not be verified');
return null;
}

console.log('App verified:', {
bundleId: appTransaction.bundleId,
environment: appTransaction.environment,
originalPurchaseDate: new Date(appTransaction.originalPurchaseDate),
});

return appTransaction;
} catch (error) {
console.error('Failed to get app transaction:', error);
return null;
}
};

Server-Side Verification

For robust anti-fraud, send deviceVerification and deviceVerificationNonce to your server:

const appTransaction = await getAppTransactionIOS();

if (appTransaction) {
await fetch('https://your-server.com/verify-app', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
deviceVerification: appTransaction.deviceVerification,
deviceVerificationNonce: appTransaction.deviceVerificationNonce,
bundleId: appTransaction.bundleId,
environment: appTransaction.environment,
}),
});
}

On your server: verify the signature using Apple's public key, check bundleId matches, validate environment, and store the nonce to prevent replay attacks.

Returns: Promise<AppTransaction | null>

interface AppTransaction {
appId: number; // App Store app identifier
appTransactionId?: string; // Unique transaction ID (iOS 18.4+)
appVersion: string; // Current app version
appVersionId: number; // App version identifier
bundleId: string; // App bundle identifier
deviceVerification: string; // Base64 device verification data
deviceVerificationNonce: string; // UUID for server verification
environment: string; // "Production" or "Sandbox"
originalAppVersion: string; // First installed version
originalPlatform?: string; // Original purchase platform (iOS 18.4+)
originalPurchaseDate: number; // Initial download timestamp (ms)
preorderDate?: number; // Pre-order date if applicable (ms)
signedDate: number; // When data was signed (ms)
}

canPresentExternalPurchaseNoticeIOS()

Check if the device can present an external purchase notice sheet. Requires iOS 18.2+.

import {canPresentExternalPurchaseNoticeIOS} from 'expo-iap';

const canPresent = await canPresentExternalPurchaseNoticeIOS();
if (canPresent) {
console.log('External purchase notice sheet is available');
}

Returns: Promise<boolean>

Platform: iOS 18.2+

Note: This notice sheet must be presented before redirecting users to external purchase links on iOS 18.2+.

presentExternalPurchaseNoticeSheetIOS()

Present an external purchase notice sheet to inform users about external purchases. This must be called before opening an external purchase link on iOS 18.2+.

import {presentExternalPurchaseNoticeSheetIOS} from 'expo-iap';

const result = await presentExternalPurchaseNoticeSheetIOS();

if (result.error) {
console.error('Failed to present notice:', result.error);
} else if (result.result === 'continue') {
// User chose to continue to external purchase
console.log('User accepted external purchase notice');
} else if (result.result === 'dismissed') {
// User dismissed the sheet
console.log('User dismissed notice');
}

Returns: Promise<ExternalPurchaseNoticeResultIOS>

interface ExternalPurchaseNoticeResultIOS {
error: string | null;
result: 'continue' | 'dismissed';
}

Platform: iOS 18.2+

See also: StoreKit External Purchase documentation

presentExternalPurchaseLinkIOS()

Open an external purchase link in Safari to redirect users to your website for purchase. Requires iOS 16.0+.

import {presentExternalPurchaseLinkIOS} from 'expo-iap';

const result = await presentExternalPurchaseLinkIOS(
'https://your-site.com/checkout',
);

if (result.error) {
console.error('Failed to open link:', result.error);
} else if (result.success) {
console.log('User redirected to external purchase website');
}

Parameters:

  • url (string): The external purchase URL to open

Returns: Promise<ExternalPurchaseLinkResultIOS>

interface ExternalPurchaseLinkResultIOS {
error: string | null;
success: boolean;
}

Platform: iOS 16.0+

Requirements:

  • Must configure iosAlternativeBilling in your Expo config plugin
  • Requires Apple approval and proper provisioning profile with external purchase entitlements
  • URLs must be configured in Info.plist via the config plugin

Example Config:

// app.config.ts
export default {
plugins: [
[
'expo-iap',
{
iosAlternativeBilling: {
countries: ['kr', 'nl'], // ISO 3166-1 alpha-2
links: {
kr: 'https://your-site.com/kr',
nl: 'https://your-site.com/nl',
},
enableExternalPurchaseLink: true,
},
},
],
],
};

See also: