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.

Key takeaways
- Apple's beginRefundRequest is a StoreKit 2 method that presents Apple's own refund sheet inside your app. The customer sees their purchase details and a list of reason codes, picks one, and the request goes to Apple. You do not build the form or decide the outcome.
- The call returns a status of success or userCancelled, or throws duplicateRequest or failed. A success status means the App Store received the request, not that it approved it. Never show a confirmed refund in your UI on success.
- After the customer submits, Apple takes up to 48 hours to approve or deny. For consumable purchases it first sends a CONSUMPTION_REQUEST to your server, and you have the usual 12 hours to answer with usage data if the customer consented.
- The outcome arrives on your server as an App Store Server Notification, the same feed you already receive. Approval is a REFUND notification, denial is REFUND_DECLINED. The in-app request routes into that feed exactly like a refund started on Apple's reportaproblem page.
- The button is available from iOS 15 and iPadOS 15, Mac Catalyst 15, and visionOS 1, so any app targeting those versions can present it today.
- The financial case is that a refund you can contest beats a chargeback you cannot. Keeping the customer inside Apple's flow triggers a CONSUMPTION_REQUEST you can answer instead of a bank chargeback that is final and carries a fee.
- Apple's placement guidance is to call it from account settings or a help menu, not a purchase screen, so an unhappy customer finds it without advertising refunds to everyone else.
Apple lets a customer request a refund without ever leaving your app. One StoreKit call, beginRefundRequest, presents Apple's own refund sheet right inside your interface, the customer picks a reason, and the request goes to Apple for review. You do not build the form, you do not touch the money, and you do not decide the outcome. What you get is a way to put a refund path where a frustrated customer already is, instead of losing them to their bank. This is the in-app refund request, and it is worth understanding before you decide whether to ship the button.
Here is the part that matters for your revenue. The button does not refund anything on its own. It opens a request, Apple takes up to 48 hours to approve or deny it, and for consumable purchases it fires a CONSUMPTION_REQUEST at your server first. So the sheet is not a giveaway. It is a funnel into the exact refund review you can already influence, and it can pull a dispute back from the card network before it becomes a chargeback you cannot contest.
What the in-app refund request sheet actually is
beginRefundRequest is a StoreKit 2 method that presents the refund request sheet for a transaction in a window scene. The signature is small: func beginRefundRequest(in scene: UIWindowScene) async throws -> Transaction.RefundRequestStatus. When you call it, the system shows a sheet with the customer's purchase details and a list of reason codes for them to choose from. Apple builds and controls that UI. You supply the scene and the transaction, nothing else.
Apple's guidance on where to put it is explicit. Call this function from account settings or a help menu, so a customer who wants a refund finds it where they would look for support. It shipped in iOS 15 and iPadOS 15, Mac Catalyst 15, and visionOS 1, so any app targeting those versions can present it today.
Two ways to open the sheet
There are two entry points. You can call beginRefundRequest(in:) on a specific transaction you already hold, which scopes the sheet to that one purchase. You can also open the sheet by product identifier when you want the customer to refund a purchase of a given product. Either way the sheet, the reason list, and the decision belong to Apple. Your job ends at presenting it and reading the result.
What the call returns, and what can go wrong
The method is async throws, so it either returns a status or throws an error. Both are short lists, and both are worth handling so your UI says something true after the sheet closes.
| Result | Type | What it means |
|---|---|---|
| success | RefundRequestStatus | The App Store received the refund request. It is submitted, not approved |
| userCancelled | RefundRequestStatus | The customer closed the sheet without submitting. Nothing was sent |
| duplicateRequest | RefundRequestError | The App Store already has a refund request for this purchase |
| failed | RefundRequestError | The submission itself failed. Let the customer try again |
What happens on your server after the customer taps submit
The sheet closing is the start of the process, not the end. Apple reviews the request and takes up to 48 hours to approve or deny it. For a consumable in-app purchase, before it decides, the App Store sends a CONSUMPTION_REQUEST notification to your server asking for usage data. If the customer consented to share that data, you answer through the Send Consumption Information endpoint. If they did not consent, Apple's own instruction is to not respond to the notification at all.
Once Apple rules, the outcome lands on your server as an App Store Server Notification. That is the same feed you already receive, and the in-app request routes into it exactly like a refund a customer starts on Apple's reportaproblem page. Nothing about the handling changes just because the request began inside your app.
| Stage | What fires | Your move | Clock |
|---|---|---|---|
| Customer submits the sheet | beginRefundRequest returns success | Record it, show pending, not refunded | Instant |
| Consumable only, Apple asks first | CONSUMPTION_REQUEST notification | Send consumption data if the customer consented, otherwise stay silent | 12 hours to respond |
| Apple approves | REFUND notification | Revoke the entitlement for that transaction | Up to 48 hours to decide |
| Apple denies | REFUND_DECLINED notification | Keep the sale, change nothing | Up to 48 hours to decide |

What the button costs you, and what it can save
A refund you can contest beats a chargeback you cannot
A customer who cannot find a refund path inside your app does not give up. They go to their bank. A card chargeback is final with the bank, it carries a dispute fee, and it takes the decision out of both your hands and Apple's. An in-app refund request keeps that same customer inside Apple's system, where a consumable purchase triggers a CONSUMPTION_REQUEST you can answer and a decision you can influence. Trading an uncontestable chargeback for a contestable Apple review is the whole financial argument for the button.
You are lowering the friction on a refund
The honest counterweight is that a visible, one-tap refund path produces more refund requests than a buried support email does. Some of those would never have happened. That is a real cost, and it is why Apple tells you to place the entry point in account settings or a help menu rather than on the purchase screen. You want the customer who is already unhappy to find it, not the customer who is merely curious.
The cost that runs the whole time is serving a refunded account
Whichever way the decision goes, the meter on delivery keeps running until you act on the outcome. Every hour a refunded entitlement stays live, you keep paying the real costs behind it: compute, model API calls, storage, and any creator or partner payout tied to that customer's usage. The in-app request does not change that. Revoking promptly on the REFUND notification does. The button is only as cheap as your handling of the notification it eventually produces.
Should you ship the in-app refund request
Put it where support lives, not where sales live
Follow Apple's placement guidance. Account settings and a help menu are the right homes. A refund link next to a paywall trains people to expect their money back, and it invites the curiosity refund you never needed to offer.
Test the full flow in the sandbox before you trust it
You can simulate the whole path in the sandbox and in Xcode's StoreKit testing, moving a request from pending to approved or declined. An approval delivers a REFUND notification to your server, and a denial delivers REFUND_DECLINED, so you can prove your handler reacts correctly before a real customer ever taps submit.
Handle every result, and never overclaim
Show pending on success, offer a retry on failed, say nothing changed on userCancelled, and treat duplicateRequest as a quiet note that the customer's earlier request still stands. The one mistake that hurts is telling a customer their refund is done when all you hold is a submitted request.
How RefundHalt handles the aftermath
The in-app sheet is Apple's. What comes after it is yours, and that is the part RefundHalt runs. When a customer submits a refund from inside your app, RefundHalt catches the CONSUMPTION_REQUEST for consumable purchases and answers it inside the 12-hour window with the usage evidence that helps Apple decide. When Apple rules, it revokes on REFUND and holds access untouched on REFUND_DECLINED, each keyed to the exact transaction. You get to offer the friendlier in-app refund path without leaving the review, the evidence, or the revocation to a manual scramble.
Frequently asked questions
- What does beginRefundRequest do?
- It presents Apple's refund request sheet inside your app for a specific transaction. The customer sees their purchase details and a list of reason codes, picks one, and the request goes to Apple. The method returns a status of success or userCancelled, or throws duplicateRequest or failed. It does not refund the purchase itself, because Apple reviews the request and takes up to 48 hours to decide.
- Does an in-app refund request refund the money right away?
- No. A success result means the App Store received the request, not that it approved it. Apple takes up to 48 hours to approve or deny, and for consumables it first asks your server for usage data through a CONSUMPTION_REQUEST notification. Show the customer a pending state on success, never a confirmed refund.
- Which iOS version supports the in-app refund request?
- iOS 15 and iPadOS 15, Mac Catalyst 15, and visionOS 1. The StoreKit 2 method beginRefundRequest(in:) is available from those versions, so any app targeting iOS 15 or later can present Apple's refund sheet from inside the app.
- Where should I put the in-app refund button?
- Apple's guidance is to call it from account settings or a help menu, not from a purchase or paywall screen. That places the refund path where an unhappy customer looks for support, without advertising refunds to customers who were not going to ask for one.
- Is an in-app refund better than a customer contacting their bank?
- Usually yes, for your revenue. A bank chargeback is final and carries a fee, and it removes both Apple and you from the decision. An in-app refund request keeps the customer in Apple's flow, where a consumable purchase triggers a CONSUMPTION_REQUEST you can answer and a review you can influence. A contestable refund beats an uncontestable chargeback.
Sources and further reading
- Apple Developer: beginRefundRequest(in:)
- Apple Developer: Transaction.RefundRequestStatus
- Apple Developer: Transaction.RefundRequestError
- Apple Developer: App Store Server Notifications V2 notificationType
- Apple Developer: Send Consumption Information
- Apple Developer: Support customers and handle refunds (WWDC21)
RefundHalt
The refund autopilot for the App Store and Google Play
Keep reading
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.
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.