---
title: "What does Product.PurchaseError.invalidOfferIdentifier mean?"
description: "Product.PurchaseError.invalidOfferIdentifier means the promotional offer ID in the purchase options is not valid. Match it to the offer in App Store Connect."
url: https://revenuedot.app/docs/errors/storekit-purchase-error-invalid-offer-identifier
---

# What does Product.PurchaseError.invalidOfferIdentifier mean?

Product.PurchaseError.invalidOfferIdentifier means the promotional offer identifier you passed in the purchase options is not valid.

## Quick facts

| | |
|---|---|
| Error | `Product.PurchaseError.invalidOfferIdentifier` |
| Where | StoreKit 2, Swift (`Product.purchase(options:)`) |
| Available since | iOS 15.0, iPadOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0 |
| What the vendor says | The promotional offer identifier provided in the purchase options is invalid. |

## Cause

- The identifier in `.promotionalOffer(offerID:keyID:nonce:signature:timestamp:)` does not match a promotional offer the App Store knows for this product.
- Apple says promotional offers must first be set up in App Store Connect, and the offers on a product appear in its `subscription.promotionalOffers` array ([promotionalOffer option](https://developer.apple.com/documentation/storekit/product/purchaseoption/promotionaloffer%28offerid:keyid:nonce:signature:timestamp:%29)).
- A typo, an offer attached to another product or an offer you never created all produce this error.

## Fix

1. Read the offer ID from `product.subscription?.promotionalOffers` instead of typing it by hand.
2. Compare the ID with the offer in App Store Connect, character by character.
3. Make sure the offer belongs to the same subscription product you are buying.
4. Sign the offer on your server only after you have picked a valid offer ID, because the ID is part of the signed text.

## Example

```swift
import StoreKit

// Take the offer ID from the product itself, so the identifier you sign and send always exists.
func offerID(for product: Product) -> String? {
    product.subscription?.promotionalOffers.first?.id
}

func buy(_ product: Product, options: Set<Product.PurchaseOption>) async {
    do {
        _ = try await product.purchase(options: options)
    } catch Product.PurchaseError.invalidOfferIdentifier {
        print("The offer ID is not one of \(product.id)'s promotional offers")
    } catch {
        print("Purchase failed: \(error)")
    }
}
```

*Compile-checked: `npm run check:snippets` type-checks this snippet with `swiftc` against the Apple SDK (macOS target, checked 2026-10-03).*

## How the RevenueDot SDK reports it

The RevenueDot `purchases-ios` fork maps `invalidOfferIdentifier`, `invalidOfferPrice`, `invalidOfferSignature` and `missingOfferParameters` to one code, `ErrorCode.invalidPromotionalOfferError` (code 34). Read the underlying error to see which of the four it was.

## Related

- [What does Product.PurchaseError.invalidOfferSignature mean?](https://revenuedot.app/docs/errors/storekit-purchase-error-invalid-offer-signature.md)
- [What does Product.PurchaseError.ineligibleForOffer mean?](https://revenuedot.app/docs/errors/storekit-purchase-error-ineligible-for-offer.md)
- [What does storeProblemError (code 2) mean in the purchases SDK?](https://revenuedot.app/docs/errors/sdk-store-problem-error.md)

## Source

- [Apple: promotionalOffer purchase option](https://developer.apple.com/documentation/storekit/product/purchaseoption/promotionaloffer%28offerid:keyid:nonce:signature:timestamp:%29)
- [Apple: Product.PurchaseError.invalidOfferIdentifier](https://developer.apple.com/documentation/storekit/product/purchaseerror/invalidofferidentifier)
- [Apple: Product.PurchaseError](https://developer.apple.com/documentation/storekit/product/purchaseerror)
- [RevenueDot purchases-ios: StoreKitError+Extensions.swift (how the SDK maps StoreKit 2 errors)](https://github.com/revenuedot/purchases-ios/blob/revenuedot/main-patches/Sources/Error%20Handling/StoreKitError%2BExtensions.swift)

Checked: 2026-10-03
