What does Product.PurchaseError.invalidOfferSignature mean?

Product.PurchaseError.invalidOfferSignature means the App Store could not validate the signature of the promotional offer.

Quick facts#

Error Product.PurchaseError.invalidOfferSignature
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 offer signature is not valid.

Cause#

  • Apple says the signature is made from the product and offer parameters plus a nonce and timestamp your server generates, joined in a fixed order with the invisible separator U+2063, and signed with your PKCS #8 private key using ECDSA with SHA-256 (Generating a signature).
  • Apple also says each payload, signature and nonce is valid for one buy request only, even if the buy fails, so reusing a signature after a failed attempt fails.
  • If you pass an app account token, Apple says it must be part of the signed text, and the UUID strings of the token and the nonce must be lowercase (promotionalOffer option).

Fix#

  1. Rebuild the signed string in the exact order Apple lists, with the separator between the parts.
  2. Create a new nonce and timestamp for every attempt, and sign again after any failed purchase.
  3. Sign with the private key whose key ID you send as keyID, and Base64-encode the DER signature.
  4. Use lowercase UUID strings for the nonce and the app account token, and sign the same values you send.
  5. Check the signature locally with the public key before you send it, as Apple suggests.

Example#

Swift
import StoreKit

// A signature works once. After a failed purchase, ask your server for a new nonce, timestamp and signature.
func buy(_ product: Product, offerID: String, keyID: String, nonce: UUID, signature: Data, timestamp: Int) async {
    let offer = Product.PurchaseOption.promotionalOffer(offerID: offerID, keyID: keyID, nonce: nonce, signature: signature, timestamp: timestamp)
    do {
        _ = try await product.purchase(options: [offer])
    } catch Product.PurchaseError.invalidOfferSignature {
        print("The App Store rejected the signature; request a fresh one from the server")
    } 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 Product.PurchaseError.invalidOfferSignature to ErrorCode.invalidPromotionalOfferError (code 34). The SDK builds and signs the offer for you when you use its promotional offer API, so this error mostly appears when you sign offers yourself.

Source#

Checked: 2026-10-03

Edit this page on GitHub ↗ View as Markdown Last updated