What does the App Store notification EXPIRED with subtype VOLUNTARY mean?
The App Store subtype VOLUNTARY on EXPIRED means the subscription ended because the customer chose to stop it.
Quick facts#
| Notification | notificationType EXPIRED, subtype VOLUNTARY |
| Where | App Store Server Notifications v2 (responseBodyV2DecodedPayload) |
| Sent by | The App Store server, to your notification URL |
| What Apple says | The subscription expired after the customer turned off subscription renewal. |
What triggers it#
- The customer cancelled earlier (you saw
DID_CHANGE_RENEWAL_STATUSwithAUTO_RENEW_DISABLED) and the paid period has now run out. - No payment problem is involved, so it is not billing related.
- The customer can come back at any time, which arrives as
SUBSCRIBEDwith subtypeRESUBSCRIBE.
What your server should do#
- Verify the
signedPayloadwith Apple's App Store Server Library (SignedDataVerifier.verifyAndDecodeNotification) before you act on anything in it. - Remove access and mark the customer as churned by choice.
- Send your win-back message or an offer, for example a promotional or win-back offer.
- Do not ask for a payment update: their payment method is not the reason.
- 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":"EXPIRED","subtype":"VOLUNTARY","data":{"environment":"Sandbox","bundleId":"com.example.app"}};
function decide(n) {
const gone = n.notificationType === "EXPIRED" && n.subtype === "VOLUNTARY";
return gone ? "remove access, mark churned by choice, send win-back" : "ignore";
}
console.log(decide(notification));Run-checked: npm run check:snippets runs this snippet with Node and compares its output with churned by choice (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 type override stores renewal off and no billing issue. The scheduler records EXPIRATION with expiration_reason: UNSUBSCRIBE, because the earlier CANCELLATION already carried that reason.
Related#
- What does the App Store notification EXPIRED mean?
- What does the App Store notification DID_CHANGE_RENEWAL_STATUS with subtype AUTO_RENEW_DISABLED mean?
- What does the App Store notification SUBSCRIBED mean?
- Which webhook events does RevenueDot send?
- Set up App Store notifications in RevenueDot
Source#
- Apple: notificationType
- Apple: subtype
- Apple: Responding to App Store Server Notifications
- Apple: app-store-server-library-node (SignedDataVerifier)
- RevenueDot server: stores/apple/notifications.ts (how the notification is applied)
- RevenueDot server: stores/apple/map.ts (how the transaction becomes a stored purchase)
- RevenueDot core: events.ts (how a change becomes a webhook event)
- RevenueDot server: services/tick.ts (the scheduled EXPIRATION)
Checked: 2026-10-03