Traditionally introductory offers were limited to eligible users who had not previously redeemed an offer for a specific product. With iOS 18.4 Apple is introducing a new API method on Product.PurchaseOption called introductoryOfferEligibility(compactJWS:) which provides more flexibility in offer redemption.

This new API method empowers developers to re-enable introductory offers for users who have previously redeemed them. The mechanism relies on a signed JSON Web Signature (JWS) returned from the developer’s server. By configuring a specific boolean property within the signature, developers can dynamically grant or modify a customer’s eligibility status for introductory offers.
In the following sections, we’ll explore how to configure server-side logic and in-app implementation to leverage the introductoryOfferEligibility(compactJWS:) method effectively.
Prerequisite
Start off by visiting the App Store Server Library GitHub repo and cloning the project.
Create a new file intro_offer_test.js in your Server code
- Create a new file
intro_offer_test.jsand add the following code to it in the server library project. - Ensure all values in the script are current with your app and App Store Connect account.
keyId: The key identifier for the App Store Connect APIissuerId: The App Store Connect issuer IDbundleId: The app’s bundle identifierproductId: The specific product ID for the subscription/purchasetransactionId: The customer’s app transaction ID- The private key downloaded from your App Store Connect account
- Run the script
npx ts-node intro_offer_test.ts - This script will generate the required JWS needed for the intro offer purchase in your iOS app.
import { IntroductoryOfferEligibilitySignatureCreator } from "@apple/app-store-server-library";
import { readFile } from "fs/promises";
import * as crypto from "crypto";
const filePath = "/Users/alexpaul/Desktop/SubscriptionKey_123456789.p8";
const keyId = "123456789";
// Your App Store Connect issuer id
const issuerId = "15501d40-84b5-4884-a43a-fa18f9e0eecf";
const bundleId = "dev.alexpaul.AppName";
const productId = "your.product.id";
// Here you grant the user access or not to redeem the intro offer
const allowIntroductoryOffer = true;
// Customer's app transaction id
const transactionId = "5000000990901234";
async function main() {
try {
const encodedKey = await readFile(filePath, "utf8");
const signatureCreator = new IntroductoryOfferEligibilitySignatureCreator(encodedKey, keyId, issuerId, bundleId);
const signature = signatureCreator.createSignature(productId, allowIntroductoryOffer, transactionId);
console.log("transactionId: ", transactionId);
console.log("productId: ", productId);
console.log("allowIntroductoryOffer: ", allowIntroductoryOffer);
// This signature is needed in the iOS app
// We use this signature in the new `introductoryOfferEligibility(compactJWS: signature)` method
console.log("signature: ", signature);
} catch (err) {
console.error("Error:", err);
}
}
main().catch(console.error);
Code breakdown:
- This code is working with Apple’s App Store Server Library to handle introductory offer eligibility for in-app purchases/subscriptions.
- It reads a private key file from a specific path (in this example it’s using the following path: /Users/alexpaul/Desktop/SubscriptionKey_123456789.p8)
- The following identifiers are needed by Apple:
keyId: The key identifier for the App Store Connect APIissuerId: The App Store Connect issuer IDbundleId: The app’s bundle identifierproductId: The specific product ID for the subscription/purchasetransactionId: The customer’s app transaction ID
- The main function:
- Reads the encoded key file asynchronously
- Creates an
IntroductoryOfferEligibilitySignatureCreatorinstance with the required credentials. - Generates a signature using:
- The product ID
- A boolean flag
allowIntroductoryOfferfor determining if the user is eligible for the introductory offer - The transaction ID
- Logs various details including:
- Transaction ID
- Product ID
- Introductory offer allowance status
- The generated signature
Implement purchase logic in your app
Use the following snippet to add purchase support for introductoryOfferEligibility(compactJWS:) in your app. You will need the signature generated from the script above.
The code snippet below does the following:
- Creates a
SetofProduct.PurchaseOptionto configure the purchase - Two key purchase options are set:
appAccountToken()- Used to track the transaction across your app
introductoryOfferEligibility()- Takes a JWS (JSON Web Signature) generated from the server script as a parameter
- The signature proves the user’s eligibility for an introductory offer
- The signature is a JWT token containing:
- Product ID
- Transaction ID
- Bundle ID
- Eligibility status
// purchase code...
let signature = "jws.signed.signature.generated.from.your.server.goes.here"
let options = Set(
[
Product.PurchaseOption.appAccountToken(appAccountToken),
Product.PurchaseOption.introductoryOfferEligibility(compactJWS: signature)
]
)
let result = try await subscription.product.purchase(options: options)
// purchase code continued...
Conclusion
By leveraging the introductoryOfferEligibility(compactJWS:) method in iOS 18.4, you can give users the ability to re-redeem introductory offers while maintaining full control over eligibility on your server.
Resources
- Apple: Implementing standard introductory offers
- Checking if a user is eligible for an intro offer
isEligibleForIntroOffer
- Checking if a user is eligible for an intro offer
- Apple:
Product.PurchaseOption - Apple:
introductoryOfferEligibility(compactJWS:) - Apple: Generating JWS to sign App Store requests
- Apple:
appTransactionID - Apple: Creating API keys to authorize API requests
- Apple: Include custom claims for introductory offer eligibility
- Apple: Testing at all stages of development with Xcode and the sandbox
- Apple: Providing subscription offers – Grow, retain, and re-acquire customers
With this new intro offer API, we now have the potential to acquire, retain, and re-acquire subscribers. Looking forward to seeing Apple’s documentation on subscription offers updated to reflect this change.


any reason not to use promotional offers to achieve the same thing?
Great question! Promo offers do address offering discounts to former subscribers, but this raises the question—will traditional promo offers gradually become obsolete? This solution is more flexible, as it allows the server to dynamically determine a user’s eligibility for an offer without the sometimes complex configuration of promo offers.