All articles
Playbook9 min read

Missed refund notifications can cost you the only refunds you're allowed to contest, and both stores let you get them back if you look in time

When your server misses a refund notification, the two refund flows that ask for your evidence close without you. Here's how Apple and Google Play retry, how far back you can recover missed refund notifications, and where the recovery gaps are.

A server rack at night with one amber warning light among steady green ones and a laptop on a cart in front, the kind of quiet outage behind missed refund notifications

Key takeaways

  • Apple retries a failed App Store Server Notifications V2 delivery five times, at 1, 12, 24, 48 and 72 hours after the previous attempt, so the second retry lands about 13 hours after the first try.
  • Apple asks developers to answer a CONSUMPTION_REQUEST within 12 hours of receiving it, so a server that misses the first delivery and the one-hour retry can see the request only after that window has likely passed.
  • Apple's Get Notification History endpoint returns V2 notifications from the past 180 days in production, 20 records per page, and its onlyFailures flag limits the results to notifications that never reached your server, plus any still being retried.
  • Google Play delivers chargeback reviews only through a PendingRefundReviewNotification, and the pendingRefundToken needed to call orders.reviewrefund within 24 hours comes in that notification. Google documents no separate API that lists pending reviews.
  • Google Cloud Pub/Sub keeps unacknowledged messages for 7 days by default, with a maximum of 31 days, and deletes a subscription after 31 days without subscriber activity unless you change its expiration period.
  • The Google Play Voided Purchases API recovers refunds, revocations and chargebacks from the past 30 days, with a quota of 6,000 queries per day and 30 queries in any 30-second period, but it can't recover a chargeback review you never answered.
  • Apple's sandbox sends each notification once with no retries, so a recovery routine that only ever ran against sandbox has never been tested against Apple's real retry schedule.

Missed refund notifications are the quiet way a developer loses the only two refund decisions they get a say in. Apple's CONSUMPTION_REQUEST and Google Play's chargeback review both start with a notification, and both close on a short clock: 12 hours on Apple, 24 hours on Google. If your server is down, slow or misconfigured when that message arrives, the store doesn't wait for you. It decides with whatever it already has.

The good news is that neither store throws the message away on the first failure. Apple retries and keeps a 180-day history you can query. Google publishes through Pub/Sub, which holds unacknowledged messages for days. The bad news is that the retry timing doesn't line up with the response windows, and one of the two flows has no recovery API at all.

How each store delivers refund notifications

Apple sends App Store Server Notifications straight to an HTTPS URL you set in App Store Connect. Google doesn't call your server directly. It publishes Real-time developer notifications to a Google Cloud Pub/Sub topic, and your subscription on that topic either pushes them to your endpoint or waits for your server to pull them.

QuestionApp StoreGoogle Play
Delivery channelHTTPS POST to your URLCloud Pub/Sub topic, push or pull
What counts as successHTTP 200 to 206Acknowledging the Pub/Sub message, or a success code on a push endpoint
Retries5 for V2, at 1, 12, 24, 48 and 72 hours after the previous attemptPub/Sub redelivers until acknowledged or the retention period ends
How long a missed message survives180 days in Get Notification History7 days by default in the subscription, up to 31 days
Refund that asks for your evidenceCONSUMPTION_REQUEST, answer within 12 hoursPendingRefundReviewNotification, answer within 24 hours
Pull-based backup for granted refundsGet Notification History, Get Refund HistoryVoided Purchases API, past 30 days
Sandbox or test behaviorOne attempt, no retriesSend Test Message from Play Console

What Apple treats as a failure

Apple's documentation is specific. Return any status from 200 to 206 and the delivery is done. Return a 40x or 50x and Apple retries. Apple considers every other code unsuccessful too. Each attempt is logged with a sendAttemptResult, and the values read like a checklist of real outages: TIMED_OUT, TLS_ISSUE, CIRCULAR_REDIRECT, NO_RESPONSE, PREMATURE_CLOSE, UNSUCCESSFUL_HTTP_RESPONSE_CODE and a few more. For TIMED_OUT, Apple's own advice is to check that your server isn't processing messages in line.

What Google treats as a failure

On a push subscription, a success response code from your endpoint is the acknowledgement. On a pull subscription, your server has to acknowledge each message it pulls, or Pub/Sub delivers it again. The default acknowledgement deadline is 10 seconds and the maximum is 600 seconds. Google's RTDN guide adds one more rule worth repeating: a notification only says that something changed, so you still call the Google Play Developer API to get the full purchase state.

Why Apple's retry schedule doesn't fit the 12-hour window

Apple's retry intervals are measured from the previous attempt, not from the first one. Add them up and you get this timeline for a single V2 notification your server keeps failing.

AttemptWait after previous attemptHours since first attempt
First deliveryNone0
Retry 11 hour1
Retry 212 hours13
Retry 324 hours37
Retry 448 hours85
Retry 572 hours157

Apple tells you to respond within 12 hours of receiving the CONSUMPTION_REQUEST. If your server is down for the first delivery and for the retry an hour later, the next attempt arrives 13 hours after the first one. Apple doesn't say whether a request first received on a retry gets a fresh 12 hours. The careful reading is to treat the first attempt as the start of the clock, which means retries alone won't save a request you missed by more than an hour.

Does Apple retry after an HTTP 400?

Yes, according to the documentation. Apple lists 40x and 50x as the codes that trigger a retry. In March 2026 a developer reported on Apple's forums that retries after 400 responses seemed to stop, then said deliveries resumed a day or two later, and Apple asked for a Feedback Assistant report. Treat the documented behavior as the rule and your own monitoring as the proof.

Recover missed App Store notifications with Get Notification History

Get Notification History returns the version 2 notifications Apple attempted to send to your server in a timespan you choose. Each record carries the full signed payload, so you can feed it to the same handler as a live notification. Each record also includes a sendAttempts array of up to six entries, one for the first try and one for each of the five retries.

  • startDate and endDate are required, and the startDate must fall within the past 180 days in production or the past 30 days in sandbox.
  • Set onlyFailures to true to get only the notifications that haven't reached your server, plus the ones Apple is still retrying.
  • Filter by one notificationType, such as CONSUMPTION_REQUEST or REFUND, or by a transactionId for a single customer, but not both in the same request.
  • Each call returns up to 20 records. Keep calling with the paginationToken while hasMore is true, and send the same request body each time.

One caveat matters for refunds. Apple says history records reflect the state of a purchase when the notification was first sent, not now. So a recovered REFUND is a fact about the past. Before you revoke access, check current state with the transaction or subscription status endpoints.

V1 notifications can't be recovered this way

Get Notification History only covers version 2. Apple retries V1 notifications three times, at 6, 24 and 48 hours after the previous attempt, and if all of them fail there's no history to replay. That's one more reason to move any app still on V1 to V2.

Recover missed Google Play refund notifications

Google's safety net is Pub/Sub itself. As long as your subscription exists, a message nobody acknowledged stays there for the retention period, 7 days by default and up to 31 days if you raise it. When your endpoint comes back, delivery resumes. Two settings can quietly defeat that.

  • A dead-letter topic moves a message out of your subscription after a set number of failed delivery attempts. The default is 5, and with the default retry policy of immediate redelivery, five attempts can pass quickly. If you use one, watch the dead-letter topic like a second inbox.
  • A subscription with no subscriber activity, such as open connections, active pulls or successful pushes, expires after 31 days by default. An endpoint that stays broken for a month can lose the subscription itself, along with everything waiting in it.

Voided refunds have a 30-day backup

For refunds that already happened, the Voided Purchases API is the pull-based backup. It lists voided purchases from the past 30 days, up to 1,000 per page, with a quota of 6,000 queries per day and 30 queries in any 30-second period for each app. Pass type 1 to include subscriptions alongside one-time products. Running it daily catches any refund your notification pipeline dropped, which keeps entitlements and revenue numbers honest.

Chargeback reviews don't have one

A PendingRefundReviewNotification is different. It carries the pendingRefundToken you must pass to orders.reviewrefund, and you have 24 hours from receiving it to send your refund preference and usage evidence. Google records your first call and ignores the rest. Google's documentation describes no endpoint that lists pending reviews, so if that Pub/Sub message is lost, your evidence for that chargeback is lost with it.

What a missed refund notification costs in money

A missed notification for a refund that's already final costs you bookkeeping. You find it later in the Voided Purchases API or Get Notification History, revoke access if your policy calls for it, and your reports catch up. Annoying, but cheap.

A missed notification in one of the two contestable flows costs real money, because the store decides without your evidence. On Apple, that means the refund decision is made without your consumption data. On Google Play, for orders placed after August 3, 2026, a lost chargeback costs you the purchase price less Play's service fee, plus the chargeback fee your buyer's bank charges. Google covers only its own service fee.

And the money you spent delivering the purchase was gone before the dispute started.

  • Compute: GPU and server time behind every generation, render or model response the buyer already used.
  • API calls: third-party requests billed the moment they fired, often per call.
  • Storage: files and media the buyer created, still on your hosting bill.
  • Payouts: revenue shares already paid to creators, affiliates or partners on that sale.

None of that comes back when a refund goes through. The evidence you send in those 12 or 24 hours is the one lever you have, and a missed notification removes it.

A recovery routine that holds up

  • Acknowledge first. Store the raw payload, return a success code, then process it in a queue.
  • Poll Apple's Get Notification History with onlyFailures every few minutes for CONSUMPTION_REQUEST, so an outage costs minutes of the 12-hour window.
  • Run a daily Get Notification History sweep with onlyFailures over the past few days for every type, and replay what you find.
  • Run a daily Voided Purchases API call with type 1 for each Google Play app, and reconcile it against the refunds you already recorded.
  • On Pub/Sub, keep message retention at 7 days or more, alert on the dead-letter topic if you have one, and alert when undelivered messages pile up.
  • Test the whole path in production too. Apple's sandbox sends each notification once with no retries, and Request a Test Notification plus Get Test Notification Status shows exactly how your server answered.
A smartphone lying face up beside a coffee mug and a notebook on a desk in early morning light, a calm start to a daily check for missed refund notifications

RefundHalt receives both stores' notifications for you, answers CONSUMPTION_REQUEST and chargeback reviews automatically inside their windows, so you aren't the one watching the clock.

Frequently asked questions

How long does Apple keep retrying a failed App Store server notification?
About six and a half days for version 2. Apple retries five times, at 1, 12, 24, 48 and 72 hours after the previous attempt, so the last retry comes 157 hours after the first delivery. Version 1 notifications get three retries, at 6, 24 and 48 hours after the previous attempt.
How far back can I recover missed App Store server notifications?
180 days in production. Apple's Get Notification History endpoint returns version 2 notifications from the past 180 days, or the past 30 days in sandbox, 20 records per call, and the onlyFailures flag limits results to notifications that never reached your server.
Does Apple retry a notification if my server returns HTTP 400?
Yes, according to Apple's documentation. Apple retries when your server returns a 40x or 50x status, and treats 200 to 206 as success. Any other status also counts as unsuccessful.
Can I recover a missed Google Play chargeback review?
Only while the Pub/Sub message still exists. The pendingRefundToken you need for orders.reviewrefund arrives in the PendingRefundReviewNotification, and Google documents no API that lists pending reviews, so the subscription's unacknowledged messages, kept 7 days by default, are your only copy.
How do I find Google Play refunds my server missed?
Call the Voided Purchases API. It returns voided purchases from the past 30 days, including refunds, revocations and chargebacks, with up to 1,000 results per page and a quota of 6,000 queries per day per app. Pass type 1 to include subscriptions.

Sources and further reading

RefundHalt

The refund autopilot for the App Store and Google Play

Keep reading

The next refund request is already on its way.

Set up RefundHalt in the time it takes to read another support email about a refund you didn't get to contest.