There's one endpoint that returns a customer's entire App Store refund history, and here's what it hands back
Apple's Get Refund History endpoint returns a customer's full App Store refund history as signed transactions. Here is every field, how the revision token paginates, why it is per customer and not per app, and what a refund you miss costs you.

Key takeaways
- Get Refund History is an App Store Server API endpoint that returns a customer's refunded in-app purchases for your app as a list of signed transactions, so you can reconcile refunds and revoke access even when a notification never reached you.
- You call GET /inApps/v2/refund/lookup/{transactionId} with any transaction id for that customer, and Apple returns their refunds across every purchase type in your app, not just the one you asked about.
- The response has three fields: signedTransactions, up to 20 JWS transactions per page sorted oldest refund first, plus a revision token and a hasMore boolean for paging.
- Store the final revision token. Pass it back next time and Apple returns only refunds newer than that point, which turns a full history dump into a short list of new rows on every run.
- Each decoded transaction carries revocationDate and revocationReason. A revocationReason of 1 means the customer refunded over an actual or perceived issue in your app, and 0 means another reason such as an accidental purchase.
- The endpoint is per customer, not per app. There is no single call that lists every refund across your whole app, so you reconcile per account from a transaction id, or read your REFUND notification feed for the app-wide view.
- The reason to wire it up is money. A refund you never catch keeps an account live, and you keep paying compute, model API calls, storage, and payouts for a customer the App Store already made whole.
Apple keeps a queryable record of every refund it has granted on a customer's account for your app, and one call returns it. The endpoint is Get Refund History, part of the App Store Server API, and it hands you that customer's full App Store refund history as a list of signed transactions. You pass a transaction id, you get back what Apple refunded, and you reconcile it against what you still have switched on.
Here is why you would bother. A refund you never see is a refund you keep paying for. The money is already gone, but the account stays live, and every hour it does you keep spending on compute, model API calls, storage, and any payout tied to that customer. Your refund notifications are meant to catch this the moment it happens. Get Refund History is the backstop for when they do not, after an outage, a deploy that dropped a webhook, or a support case where you need the whole picture in one call.
What the App Store refund history endpoint returns
You call GET /inApps/v2/refund/lookup/{transactionId} against the App Store Server API, signed with the same JWT you use for every other call to it. The transaction id in the path can be any transaction for the customer. Apple reads it as an identity, not a filter, and returns that customer's refunded purchases across your whole app: consumables, non-consumables, auto-renewable and non-renewing subscriptions alike. The older V1 of this endpoint returned up to 50 refunds in a single response and is deprecated. The current version pages, so you handle customers with long histories without a giant payload.
The response is three fields
| Field | What it holds |
|---|---|
| signedTransactions | Up to 20 refunded transactions for this customer, each a signed JWS you verify and decode. Sorted oldest refund first, by revocationDate. An empty array means the customer has no refunds in your app |
| revision | A pagination token. Pass it back to get the next page, and keep the final one to fetch only new refunds next time |
| hasMore | True when Apple holds more refunded transactions than this page returned, so you call again with the revision |
What one refunded transaction tells you
Each entry in signedTransactions is a JWS. Verify it against Apple's certificate chain, decode it, and you have an ordinary transaction payload with the refund fields filled in. These are the ones that matter here.
| Field | What it tells you |
|---|---|
| transactionId | The id of the refunded transaction, your join key back to the purchase you recorded |
| originalTransactionId | The id of the first purchase in the chain, how you tie a subscription's renewals together |
| productId | The product that was refunded, so you revoke the right entitlement and nothing else |
| revocationDate | The UNIX time, in milliseconds, that Apple refunded the transaction |
| revocationReason | Why Apple refunded it. 1 means an actual or perceived issue with your app, 0 means another reason such as an accidental purchase |
| price, currency | The amount, in milliunits, and its ISO 4217 currency code, so you can total the money returned |
| appAccountToken | The UUID you attached at purchase, the cleanest way to map a refund back to your own user |
The revision token is how you stop re-reading the whole list
The naive way to use this endpoint is to look up a customer and walk every page every time. That works, and on a customer with fifty refunds it is fifty rows you already knew about plus the one new one. The revision token exists to kill that waste. Each response carries a revision. When hasMore is true, you pass it back to get the next page. When you reach the end, you keep the last revision you saw.
What this endpoint will not do
There is one expectation to drop before you build on it. Get Refund History is per customer, not per app. You cannot ask it for every refund your app took last week. It answers one question, which refunds does this account have, and you have to arrive with a transaction id for that account to ask it. Developers hit this wall constantly and go looking for a whole-app refunds endpoint that does not exist.
The app-wide view lives somewhere else. Your App Store Server Notifications feed sends a REFUND notification the moment Apple grants each one, and Get Notification History lets you replay that feed filtered to refund types over a date range. So the division is clean. Notifications and their history give you the app-wide stream. Get Refund History gives you one account's authoritative list, on demand, which is what you want at a support desk or after an outage.

What a missed refund costs you in money
The endpoint is plumbing. The bill is the reason you lay the pipe. Every refund in that list is money already returned, and the only variable left in your control is how long you keep spending on an account that no longer pays.
You keep paying to serve a refunded account
The purchase price is gone the instant Apple grants the refund. What keeps running is the cost of delivery. For an app that does real work per user, that is compute, model API calls, storage, and any creator or partner payout tied to their usage. A refunded customer whose access you never cut is a subscription you fund out of pocket. Reconciling against Get Refund History and revoking on what you find is how you shut that meter off when a notification slipped through.
A refund reason of 1 is a defect report in disguise
revocationReason costs you twice if you ignore it. The first cost is the refund itself. The second is every future refund from the same cause. When a product keeps coming back with revocationReason 1, an actual or perceived issue in your app, Apple is handing you a labeled sample of what makes customers ask for their money back. Trend it by product and you can fix the leak instead of paying it out one refund at a time.
Catching it late still beats not catching it
A chargeback is final with the bank and, on the other store, now carries a fee the developer eats. An App Store refund is not that. It is settled, but the entitlement is yours to revoke the moment you know. So even a refund you find days late through this endpoint is worth finding. You cannot claw the money back, but you can stop the spend that was still running behind it.
How this fits with notifications, and with Google
Think of the pieces as one system. The REFUND notification is the live signal, pushed to your server as Apple decides. Get Refund History is the pull-based source of truth for a single customer, the call you make when the push failed or when a human needs the full account in front of them. On the Google Play side the shape is the same idea with different names: a VoidedPurchaseNotification pushes in real time, and the Voided Purchases API is the list you pull. Both stores give you a stream and a ledger. The mistake is trusting only the stream, because streams drop.
Wiring it up the RefundHalt way
The loop is small once every piece is in place. Take a REFUND notification as the trigger. Reconcile against Get Refund History so a dropped webhook never leaves a refunded account live. Decode each transaction, key it on appAccountToken or transactionId back to your user, read revocationReason so a defect refund gets flagged and not just filed, and revoke the exact entitlement rather than the whole account. Page with the revision token so you read new refunds, not old ones.
This is the part RefundHalt runs for you. It listens to the refund notifications, falls back to Get Refund History when it needs the authoritative list, verifies every signed transaction, revokes the precise purchase, and keeps the revision so each pass reads only what changed. You get access cut in seconds and a clean record of who was refunded, for what, and why, without standing up the polling and the JWS verification yourself.
Frequently asked questions
- How do I see every refund across my whole app, not just one customer?
- You cannot with Get Refund History, because it is per customer and needs a transaction id for the account you are asking about. For the app-wide view, use your App Store Server Notifications feed, which sends a REFUND notification for each refund as Apple grants it, and Get Notification History to replay that feed filtered to refund types over a date range.
- How many refunds does the Get Refund History endpoint return?
- The current version returns up to 20 refunded transactions per page, sorted with the oldest refund first, and pages through the rest with a revision token when hasMore is true. The deprecated V1 endpoint returned up to 50 in a single response. There is no cap on the total, so a customer with a long history simply spans more pages.
- What is the revision token for?
- It is how you paginate and how you avoid re-reading a customer's whole history every time. Each response includes a revision. You pass it back to fetch the next page, and you store the final one so your next lookup returns only refunds newer than that point. That keeps a scheduled reconciliation to a short list of new rows.
- What does revocationReason mean in a refunded transaction?
- It is why Apple refunded the transaction. A value of 1 means the customer refunded because of an actual or perceived issue within your app, and 0 means another reason, such as an accidental purchase. revocationDate tells you when the refund happened, in UNIX milliseconds. Reading revocationReason lets you separate a product defect from a one-off remorse refund.
- Do I still need this if I already handle REFUND notifications?
- Yes, as a backstop. Notifications are the live signal, but a push can fail to arrive during an outage, a bad deploy, or a webhook change, and a missed refund leaves a refunded account live and costing you money. Get Refund History is the pull-based source of truth you reconcile against so nothing stays switched on that Apple already refunded.
Sources and further reading
- Apple Developer: Get Refund History (App Store Server API)
- Apple Developer: RefundHistoryResponse
- Apple Developer: JWSTransactionDecodedPayload (revocationDate, revocationReason)
- Apple Developer: Get Refund History V1 (deprecated)
- Apple Developer: App Store Server Notifications V2 notificationType (REFUND)
- Apple Developer: Support customers and handle refunds (WWDC21)
RefundHalt
The refund autopilot for the App Store and Google Play
Keep reading
Your app can show an in-app refund request sheet, and here's what Apple does after a customer taps submit
Apple's in-app refund request lets a customer ask for a refund without leaving your app, on a sheet Apple builds and reviews. Here is what beginRefundRequest returns, the CONSUMPTION_REQUEST and 48-hour clocks it starts on your server, and whether the button is worth shipping.
When a Google Play purchase is refunded or charged back, the Voided Purchases API is how you find out
Google Play voids a purchase quietly when it is refunded or charged back. The Voided Purchases API is the list of those orders, so you can revoke access. Here is every field, the 30-day window, the revoke option that hides orders, and what it costs.