All articles
Playbook7 min read

Three App Store refund notifications arrive after Apple decides, and REFUND_REVERSED gives the sale back

Apple sends four refund messages through App Store Server Notifications V2, and most apps handle only two. REFUND tells you to revoke, REFUND_DECLINED means keep the sale, and REFUND_REVERSED hands the sale back and asks you to restore what you took. Here is what each one requires.

A smartphone showing a payment receipt beside a returned envelope and a single coin, illustrating the App Store refund notifications Apple sends after it decides a refund

Key takeaways

  • Apple sends four refund-related messages through App Store Server Notifications V2. CONSUMPTION_REQUEST asks for your evidence, and REFUND, REFUND_DECLINED, and REFUND_REVERSED report the outcome after Apple has already decided.
  • A REFUND notification means the App Store refunded the transaction. It carries revocationDate and revocationReason, and it is your signal to revoke the entitlement tied to that one transaction, not every purchase of that product.
  • revocationReason has two values. 1 means the refund was granted due to an issue with your product, and 0 means it was granted for another reason. The issue value is a quality signal worth logging and trending.
  • REFUND_DECLINED means Apple rejected the customer's refund. You keep the sale and change nothing, which is only safe if you did not revoke access before the decision was final.
  • REFUND_REVERSED means Apple reversed a refund it previously granted, usually after the customer disputes it. The revocation fields drop off the transaction, and Apple's own instruction is that if you revoked content, you need to reinstate it.
  • Respond to all four notifications with HTTP 200. If your server was down and missed one, the Get Refund History endpoint lets you look up refunded transactions by transaction id and reconcile.
  • A refund for a past subscription period does not always mean access should end. If a newer paid period is still active, revoking on the old transaction cuts off a customer who is current.

Apple decides your refund, and then it keeps talking. Once the outcome is set, the App Store sends your server one of three App Store refund notifications, and each one asks for a different move. A REFUND says the money is gone and you should pull access. A REFUND_DECLINED says the customer lost the request and you keep the sale. A REFUND_REVERSED says Apple undid a refund it had already granted, so the sale is yours again and you need to give back whatever you took away. Most apps wire up the first one and quietly ignore the other two. That is how a paying customer ends up locked out of something they paid for.

These three are separate from CONSUMPTION_REQUEST, the only refund message that asks you to reply. The post-decision notifications do not want an argument. They want an HTTP 200 and the right change to the customer's access. Here is what each one means, the exact fields that carry the facts, and where the money leaks when you handle them wrong.

The four refund notifications, and which one wants a reply

App Store Server Notifications V2 is a single feed. You point it at one URL and Apple sends every notification type to it, so you already receive all four refund messages whether or not you handle them. Four of the types touch refunds, and only one of them is a question.

NotificationWhat Apple is telling youYour actionReply expected
CONSUMPTION_REQUESTA customer asked for a refund and Apple wants your dataSend Consumption Information within 12 hoursYes, real data
REFUNDThe App Store refunded the transactionRevoke the entitlement for that transactionNo, HTTP 200
REFUND_DECLINEDThe App Store declined the refundKeep access, change nothingNo, HTTP 200
REFUND_REVERSEDApple reversed a refund it had grantedReinstate the content you revokedNo, HTTP 200

What a REFUND notification actually tells you

REFUND fires when the App Store has successfully refunded a transaction to a customer. It applies to every purchase type: a consumable, a non-consumable, an auto-renewable subscription, and a non-renewing subscription. The signed transaction inside the notification now carries two fields it did not have before the refund, and those two fields are the whole story.

revocationDate and revocationReason carry the facts

revocationDate is the UNIX time, in milliseconds, that the App Store refunded the transaction or revoked it. revocationReason tells you the category of the refund, and it takes exactly two values.

revocationReasonApple's meaningWhat to read into it
1The refund was granted due to an issue with the productA quality or delivery signal. Log it, trend it, and look for a pattern in one product or one build
0The refund was granted for another reasonAn ordinary refund. Revoke the entitlement and move on

The presence of a revocationDate on a transaction is itself the flag. If you fetch a transaction later and it has a revocationDate, that purchase has been refunded, notification or no notification. Read the reason alongside it so a wave of value-1 refunds on a single release does not slip past you as noise.

Revoke by transaction, not by product

The trap here is revoking too much. A REFUND names one transaction. It does not tell you to disable every purchase the customer ever made of that product id. Apple's own guidance is to check what access the customer still holds before you cut anything, because entitlements overlap. The classic case is a subscription: a refund lands on last month's renewal while this month's renewal is active and fully paid. Revoke on the product and you have just cut off a current, paying customer over a refund for a period that already ended.

REFUND_DECLINED means you already won, so do not undo it

REFUND_DECLINED arrives when the App Store declined the customer's refund request. The customer asked, Apple said no, and you keep the sale. On its face there is nothing to do, and that is the point. The mistake this notification exposes is a different one: revoking access early.

If your code reacts to the CONSUMPTION_REQUEST by pulling the customer's access before Apple has ruled, a REFUND_DECLINED is the moment that decision blows up. Apple kept your money, and you locked out a customer whose refund was denied. That customer now pays for a product they cannot use, opens a support ticket, and remembers it. The fix is a rule, not a feature: revoke on REFUND, never on the request. REFUND_DECLINED is simply Apple confirming that early revocation would have been the wrong call.

REFUND_REVERSED is the notification that pays you back

REFUND_REVERSED is the one almost nobody handles, and it is the one that returns money to you. Apple sends it when it reverses a refund it previously granted, typically after the customer disputes that refund. The revocation fields that a REFUND added to the transaction are removed again, so the purchase reads as paid once more. Apple states the developer's job in one line: if your app revoked content or services as a result of the related refund, it needs to reinstate them. It applies to any purchase type, from a consumable to an auto-renewable subscription.

The weeks-later problem

The real question developers raise, on Apple's own forums, is timing. A REFUND_REVERSED can land weeks after the original REFUND, long after a subscription period has expired. Do you restore access then? Reinstate what the transaction actually grants, scoped to what that transaction covers. For a consumable or a non-consumable, turn the unlock back on. For a subscription period that has already elapsed, you are not handing out new time, you are correcting the record so the customer's history is accurate and any entitlement that is still valid goes live again. Restore the specific transaction, and your overlap logic decides what is currently active.

A coin being placed back beside a smartphone, illustrating a reversed App Store refund that restores the sale to the developer

Where the money is in getting this right

Each of these notifications maps to a real number, and the cost of mishandling it is not the sale price alone.

REFUND: stop paying to serve a refunded customer

The sale price is gone the moment REFUND arrives. What you can still control is the cost of continuing to deliver. Every hour that a refunded entitlement stays live, you keep spending on the things the customer no longer pays for: compute, model API calls, storage, and any creator or partner payout tied to their usage. Revoking promptly on REFUND stops that meter. Ignoring the notification means you fund a product for someone the store already made whole.

REFUND_DECLINED: do not turn a win into a refund of goodwill

When you revoke early and the refund is later declined, you kept the sale on paper and lost it in practice. The customer who paid cannot use the product, so you inherit a support conversation and, often, a discretionary refund to make it right. That is paying twice for one sale that was never in danger. Handling REFUND_DECLINED correctly costs nothing, which is exactly why leaving access untouched until REFUND is the cheapest rule you can adopt.

REFUND_REVERSED: the worst pairing is their money and their access both gone

Ignore REFUND_REVERSED and you reach the worst outcome on the board. You have been paid, and the customer has nothing. They contacted their bank once already to reverse the refund, and a person locked out of a product they are now charged for is a person likely to contact the bank a second time. That next dispute can become a card chargeback, which is bank-final and costs more than the sale ever did. Reinstating access the moment REFUND_REVERSED arrives is the cheapest insurance in the whole refund flow.

What to wire up

The handling is small once the model is right. Key entitlements on the transaction id so every notification points at one purchase. On CONSUMPTION_REQUEST, send your data inside 12 hours. On REFUND, revoke that transaction. On REFUND_DECLINED, do nothing. On REFUND_REVERSED, reinstate. Return HTTP 200 quickly on all of them and do the access change on your own time.

For the gap that notifications leave, use the Get Refund History endpoint. If your server was down during an outage and missed a REFUND, call the App Store Server API refund lookup for a transaction id, at /inApps/v2/refund/lookup/{transactionId}, and read back the signed transactions with their revocationDate and revocationReason. It reconciles one transaction at a time and pages through a customer's refunded purchases, so a missed webhook does not become a permanently mis-set entitlement.

This is the part RefundHalt runs for you. It listens for all four types, revokes on REFUND, holds access untouched on REFUND_DECLINED, and reinstates automatically on REFUND_REVERSED, each keyed to the exact transaction. A reversed refund does not sit in a queue while a paying customer stays locked out, and a declined one never triggers a revoke you would have to walk back.

Frequently asked questions

What is the difference between REFUND and REFUND_REVERSED?
REFUND means the App Store refunded a transaction and you should revoke that entitlement, while REFUND_REVERSED means Apple undid a refund it had granted and you should reinstate the content you revoked. The two are a pair: a purchase can go REFUND and then, if the customer's dispute is overturned, REFUND_REVERSED. Key your access changes on the transaction id so each notification acts on the right purchase.
Do I need to send anything back for a REFUND notification?
No. You respond to REFUND, REFUND_DECLINED, and REFUND_REVERSED with an HTTP 200 and no body. Only CONSUMPTION_REQUEST asks you to send data, and it does so through the Send Consumption Information endpoint within 12 hours. The other three are Apple reporting a decision, not asking a question.
What should I do when I get a REFUND_DECLINED notification?
Nothing changes, because the customer's refund was denied and you keep the sale. The only way REFUND_DECLINED causes work is if you revoked access early, before Apple ruled. Revoke on REFUND rather than on the CONSUMPTION_REQUEST, and a REFUND_DECLINED becomes a confirmation that access was correctly left alone.
Should I restore access when REFUND_REVERSED arrives weeks after the refund?
Yes, reinstate the entitlement that specific transaction grants. Apple states that if your app revoked content because of the related refund, it needs to reinstate it. For a consumable or non-consumable, turn the unlock back on. For a subscription period that has already expired you are correcting the record, not granting new time, so your overlap logic still decides what is currently active.
How do I catch a refund notification my server missed?
Use the App Store Server API Get Refund History endpoint, which looks up refunded transactions for a customer by transaction id at /inApps/v2/refund/lookup/{transactionId}. It returns signed transactions with revocationDate and revocationReason, so after an outage you can reconcile access without waiting for a notification that already fired. It handles one transaction id per call and pages through the customer's refunded purchases.

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.