---
title: "What does the App Store notification EXPIRED with subtype BILLING_RETRY mean?"
description: "EXPIRED with subtype BILLING_RETRY means billing retry ended without recovering the subscription. Remove access and ask the customer for a new payment method."
url: https://revenuedot.app/docs/notifications/apple-expired-billing-retry
---

# What does the App Store notification EXPIRED with subtype BILLING_RETRY mean?

The App Store subtype BILLING_RETRY on EXPIRED means the App Store gave up charging the customer and the subscription ended.

## Quick facts

| | |
|---|---|
| Notification | `notificationType` `EXPIRED`, `subtype` `BILLING_RETRY` |
| Where | App Store Server Notifications v2 (`responseBodyV2DecodedPayload`) |
| Sent by | The App Store server, to your notification URL |
| What Apple says | The subscription expired because it failed to renew before the billing retry period ended. |

## What triggers it

- A renewal failed, and Apple retried billing for up to 60 days without success.
- Any grace period had already ended (you saw `GRACE_PERIOD_EXPIRED`) or the app had no grace period.
- The customer did not fix their payment method, and did not cancel.

## 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. Remove access and mark the churn as involuntary.
3. Send a win-back message that asks for a new payment method and links to the customer's payment settings.
4. Report it separately from voluntary churn in your metrics.
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":"EXPIRED","subtype":"BILLING_RETRY","data":{"environment":"Sandbox","bundleId":"com.example.app"}};

function decide(n) {
  const gaveUp = n.notificationType === "EXPIRED" && n.subtype === "BILLING_RETRY";
  return gaveUp ? "remove access, mark involuntary churn, ask for a new payment method" : "ignore";
}

console.log(decide(notification));
```

*Run-checked: `npm run check:snippets` runs this snippet with Node and compares its output with `involuntary churn` (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 marks a billing issue on the stored purchase. The scheduler records `EXPIRATION` with `expiration_reason: BILLING_ERROR`, which is how RevenueDot (like RevenueCat) tells involuntary churn from a voluntary cancel.

## Related

- [What does the App Store notification EXPIRED mean?](https://revenuedot.app/docs/notifications/apple-expired.md)
- [What does the App Store notification DID_FAIL_TO_RENEW mean?](https://revenuedot.app/docs/notifications/apple-did-fail-to-renew.md)
- [What does the App Store notification GRACE_PERIOD_EXPIRED mean?](https://revenuedot.app/docs/notifications/apple-grace-period-expired.md)
- [Which webhook events does RevenueDot send?](https://revenuedot.app/docs/api/webhook-events.md)
- [Set up App Store notifications in RevenueDot](https://revenuedot.app/docs/guides/app-store.md)

## Source

- [Apple: notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype)
- [Apple: subtype](https://developer.apple.com/documentation/appstoreservernotifications/subtype)
- [Apple: Responding to App Store Server Notifications](https://developer.apple.com/documentation/appstoreservernotifications/responding-to-app-store-server-notifications)
- [Apple: app-store-server-library-node (SignedDataVerifier)](https://github.com/apple/app-store-server-library-node)
- [RevenueDot server: stores/apple/notifications.ts (how the notification is applied)](https://github.com/revenuedot/revenuedot/blob/main/apps/server/src/stores/apple/notifications.ts)
- [RevenueDot server: stores/apple/map.ts (how the transaction becomes a stored purchase)](https://github.com/revenuedot/revenuedot/blob/main/apps/server/src/stores/apple/map.ts)
- [RevenueDot core: events.ts (how a change becomes a webhook event)](https://github.com/revenuedot/revenuedot/blob/main/packages/core/src/events.ts)
- [RevenueDot server: services/tick.ts (the scheduled EXPIRATION)](https://github.com/revenuedot/revenuedot/blob/main/apps/server/src/services/tick.ts)

Checked: 2026-10-03
