What does the App Store notification OFFER_REDEEMED mean?

The App Store notification OFFER_REDEEMED means a customer with an active subscription redeemed a promotional offer or an offer code.

Quick facts#

Notification notificationType OFFER_REDEEMED
Where App Store Server Notifications v2 (responseBodyV2DecodedPayload)
Sent by The App Store server, to your notification URL
What Apple says A customer with an active subscription redeemed a subscription offer. UPGRADE applies at once, DOWNGRADE at the next renewal date, and no subtype means an offer on the current subscription.

What triggers it#

  • The customer redeemed a promotional offer or offer code while subscribed. First-time and returning subscribers redeeming an offer arrive as SUBSCRIBED, and offers on consumables or non-consumables as ONE_TIME_CHARGE.
  • Subtype UPGRADE: the offer upgraded the plan, effective immediately. DOWNGRADE: the plan changes at the next renewal.
  • Apple says to check offerType in the transaction to see what kind of offer it was.

What your server should do#

  1. Verify the signedPayload with Apple's App Store Server Library (SignedDataVerifier.verifyAndDecodeNotification) before you act on anything in it.
  2. Read offerType and offerIdentifier from the signed transaction and record which offer was used.
  3. Update the entitlement for an UPGRADE; schedule it for a DOWNGRADE.
  4. Mark the customer as having used the offer, so you do not show it again.
  5. Answer HTTP 200 (any code from 200 to 206) once you have stored the notification. Apple retries other answers five times, at 1, 12, 24, 48 and 72 hours after the previous attempt, so make your handler safe to run twice (the payload carries a notificationUUID).

Example#

JavaScript
// Run on the decoded payload, after you verified Apple's signature.
const notification = {"notificationType":"OFFER_REDEEMED","data":{"environment":"Sandbox","bundleId":"com.example.app"}};

function decide(n) {
  if (n.notificationType !== "OFFER_REDEEMED") return "ignore";
  const when = { UPGRADE: "now", DOWNGRADE: "at the next renewal" }[n.subtype] ?? "on the current plan";
  return "record the offer, apply it " + when;
}

console.log(decide(notification));

Run-checked: npm run check:snippets runs this snippet with Node and compares its output with record the offer (checked 2026-10-03).

How RevenueDot handles it#

RevenueDot verifies Apple's signature, checks the bundle ID, stores the notification and answers 200. A purchase RevenueDot has not seen is applied only when the app's Track new purchases from server-to-server notifications setting is on. The signed transaction carries the offer, and RevenueDot stores its identifier as the period's offer, which webhooks send as offer_code. The period type follows the offer (a free offer is TRIAL, a paid introductory or offer code period is INTRO, a paid promotional or win-back period is NORMAL). A plan change gives the same PRODUCT_CHANGE rules as DID_CHANGE_RENEWAL_PREF.

Source#

Checked: 2026-10-03

Edit this page on GitHub ↗ View as Markdown Last updated