Apple의 환불 프로세스를 다뤄본 적이 있다면, 문서에 언급된 12시간 응답 기한을 본 적이 있을 겁니다. 하지만 실제로 어떻게 응답해야 하는지는 문서에서 항상 명확하게 설명해 주지 않습니다.
CONSUMPTION_REQUEST는 하나의 알림이며, 이에 대한 응답은 별도의 API를 호출하는 작업으로, 자체 키를 사용하고 2021년 이후 형태가 두 차례 바뀐 본문(body)을 가지고 있습니다. 예전에 저희는 알림 자체에 담긴 내용을 다룬 적이 있습니다. 이번 글에서는 그다음 단계, 즉 실제로 응답해야 하는 과정을 살펴봅니다.
핵심 요약
App Store Server API에 보내는 PUT 요청 하나가 전체 메커니즘입니다.
각 요청은 인앱 구매 키로 생성한 JWT로 서명되며, 발급 후 60분이 지난 토큰은 거부됩니다.
v2 본문에는 5개의 필드가 있으며, 그중 3개는 필수입니다.
Apple은 202 응답만 보낼 뿐 그 이상은 알려주지 않습니다. 환불 결정은 나중에 별도의 알림으로 전달됩니다.
알림이 도착한 시점부터 12시간이며, 이 기한 내에는 다시 전송할 수 있습니다.
Apple 소비 요청이란 무엇인가요?
전문 용어를 걷어내고 보면, Apple 소비 요청은 결국 하나의 질문입니다. 고객이 Apple에 환불을 요청했고, Apple이 그 요청에 답하기 전에 여러분의 서버에 묻는 것입니다. 이 고객이 결제한 만큼의 것을 받았는지, 그리고 그것을 사용했는지 말이죠.
Apple은 여러분이 전달한 정보를, 이 고객이 얼마나 자주 환불을 요청하는지 등 이미 알고 있는 정보와 함께 반영해 스스로 결정을 내립니다.
사람들은 이 마지막 부분을 끊임없이 오해합니다. 여러분은 승인이나 거부를 하는 것이 아니라, 사실 정보를 전달하는 것뿐입니다.
Apple이 보내는 CONSUMPTION_REQUEST는 App Store Connect에 등록한 서버 URL로, 버전 2 서버 알림 형태로 전달됩니다.
2021년에는 소모성 제품(consumables)에만 발생했습니다. 2024년에 구독이 추가되었고, 2025년 12월에는 Apple이 이 엔드포인트의 버전 2를 발표했는데, 문서에 따르면 비소모성 제품과 갱신되지 않는 구독을 포함한 모든 제품 유형을 다룬다고 합니다. 알림 레퍼런스 페이지에는 아직 원래의 두 가지만 나열되어 있지만, 저라면 최신 페이지를 믿고 네 가지 유형 모두에 대응하도록 구현하겠습니다.
이름에 관해 한 가지 짚자면, 사람들은 'Apple 환불 API'를 검색하지만 그런 별도의 API는 존재하지 않습니다. 이 호출은 App Store Server API의 일부입니다.
제출하기 전에 준비해야 할 것
이 네 가지 중 세 가지는 급하게 준비할 수 없으므로, 첫 요청을 보내기 전에 이 내용을 먼저 읽어두시기 바랍니다.
첫 번째는 키입니다. 정확히는 App Store Connect의 Users and Access에서 생성하는 인앱 구매 키입니다. Apple은 .p8 파일을 딱 한 번만 제공하며, 이 키의 키 ID와 여러분의 발급자 ID(issuer ID)도 필요합니다.
두 번째는 트랜잭션 ID(Apple이 제공하는 값)를 데이터베이스에 있는 특정 사용자(여러분에게 필요한 값)로 연결할 방법입니다. 가장 깔끔한 방법은 구매 시점에 설정하는 appAccountToken이며, 이후 해당 트랜잭션과 관련된 모든 알림에 함께 반환됩니다.
이 용어가 처음이라면, 먼저 appAccountToken이 환불 방어에 왜 중요한지를 읽어보세요. 이 매핑이 없다면 이 글의 나머지 내용은 그저 이론에 불과합니다.
세 번째는 동의(consent)입니다. 이것은 새벽 2시에 급하게 해결할 수 없는 부분입니다.
Apple 문서에는 고객이 이 데이터를 공유하는 데 동의하지 않았다면 아예 응답하지 말라고 명확히 적혀 있습니다. 또한 더 법률적인 표현으로, 유효한 동의로 인정되는 기준은 운영 지역에 따라 다르며 Apple이 이에 대해 조언해줄 수 없다고도 밝히고 있습니다.
보통은 이용약관에 옵트인(opt-in) 문구를 넣는 방식으로 해결하며, 이는 요청이 들어오기 전에 이미 마련되어 있어야 합니다.
네 번째는 트랜잭션 ID 자체인데, 이는 알림이 도착하고 Apple의 서명을 확인한 후에야 얻을 수 있습니다.
소비 정보를 제출하는 방법: 단계별 가이드
'send consumption information apple'을 검색하는 대부분의 사람들은 필드를 나열한 뒤 다소 갑작스럽게 끝나는 Apple 레퍼런스 페이지에 도달합니다. 실제 진행 순서는 다음과 같습니다.
먼저 알림이 실제로 유효한지 확인합니다. signedPayload는 JWS이며, Apple의 App Store Server Library가 이를 검증하고 transactionId와 appAccountToken을 포함한 디코딩된 트랜잭션 정보를 반환합니다.
여러분 쪽 시스템에서 해당 구매 내역을 찾습니다. 확인해야 할 것은 제품을 전달했는지, 대략 어느 정도 비율이 사용되었는지, 사전에 샘플이나 체험판을 제공했는지, 그리고 Apple이 어떤 결정을 내려주었으면 하는지입니다.
이제 토큰을 생성합니다. 헤더: alg는 ES256, kid는 여러분의 키 ID, typ은 JWT입니다. 페이로드: iss는 여러분의 발급자 ID, iat은 현재 시각, exp는 최대 60분 후, aud는 문자열 그대로 appstoreconnect-v1, bid는 여러분의 번들 ID입니다. 정확한 구조는 Apple의 JSON Web Token 생성 가이드에 나와 있지만, 라이브러리가 이를 대신 생성해 줍니다.
PUT 요청을 보냅니다. 프로덕션 URL은 https://api.storekit.apple.com/inApps/v2/transactions/consumption/{transactionId}이며, Authorization 헤더에 JWT를 Bearer 토큰으로 넣고, 다음과 같은 형태의 본문을 함께 전송합니다.
{
"customerConsented": true,
"deliveryStatus": "DELIVERED",
"consumptionPercentage": 94000,
"sampleContentProvided": false,
"refundPreference": "DECLINE"
}
Apple은 202 Accepted로 응답하며, 그게 전부입니다. 시간, 본문, 트랜잭션 ID를 로그로 남겨두세요. 한 달 후에 누군가 "이 건에 대해 Apple에 뭐라고 전달했는지" 물어볼 테니까요.
그런 다음 기다립니다. 결정은 REFUND 또는 REFUND_DECLINED 알림으로 도착합니다. REFUND라면 당일에 콘텐츠나 사용 권한(entitlement)을 회수하세요.
그러니 Apple이 실제로 활용할 소비 정보를 어떻게 제출해야 하는지 궁금했다면, 바로 이것입니다. 요청 하나, 필드 다섯 개, 기한 내 제출.
또한 Apple은 그 12시간 이내에 업데이트된 페이로드를 다시 보내는 것도 허용하므로, 첫 번째 요청에 정보가 절반만 담겨 있었다면 두 번째 요청을 보내세요.
필드 이름만으로는 알 수 없는 몇 가지가 있습니다. consumptionPercentage는 밀리유닛(milliunits) 단위로 표시되는데, 이 부분에서 많이 혼동합니다. 94000은 94%, 100000은 100%를 의미하며, deliveryStatus가 DELIVERED가 아닌 경우에는 반드시 0이어야 합니다.
전달되지 않은 경우(undelivered)에 사용할 수 있는 옵션은 UNDELIVERED_QUALITY_ISSUE, UNDELIVERED_WRONG_ITEM, UNDELIVERED_SERVER_OUTAGE, UNDELIVERED_OTHER입니다.
refundPreference는 선택 항목이며 GRANT_FULL, GRANT_PRORATED, DECLINE 중 하나를 사용할 수 있습니다. 자동 갱신 구독에 대해 GRANT_PRORATED를 사용할 경우에는 비율(percentage)을 생략해도 되며, Apple이 남은 기간을 기준으로 직접 계산합니다.
Send Consumption Information 레퍼런스 문서에 전체 오류 목록이 나와 있습니다.
응답 코드가 의미하는 것
좋은 응답은 202 하나뿐입니다. 그 외의 모든 응답은 Apple이 무엇이 잘못되었는지 대개 이름까지 명시해서 알려주는 것입니다.
400번대 응답이 가장 유용한데, 본문에 오류 이름이 명시되어 있기 때문입니다.
ConsumptionPercentageOutOfRangeError는 거의 항상 94000 대신 94를 보낸 경우입니다.
UndeliveredConsumptionPercentageNonZeroError는 아이템이 전달되지 않았다고 하면서도 일부 사용되었다고 주장한 경우를 의미합니다.
InvalidCustomerConsentedError는 이름 그대로입니다.
401은 토큰 문제입니다. 잘못된 키, 1시간을 초과한 만료 시간, 일치하지 않는 번들 ID, 혹은 서버 시계가 많이 틀어져 iat이 미래 시점처럼 보이는 경우 등입니다.
404는 트랜잭션 ID를 찾을 수 없다는 뜻이며, 저는 항상 샌드박스 트랜잭션이 프로덕션 URL로 전송되었는지(또는 그 반대인지)부터 확인합니다.
429는 요청 제한(rate limit)입니다. 프로덕션에서는 앱당 초당 50회, 샌드박스에서는 그 10분의 1입니다. 재시도하기 전에 Retry-After 헤더를 확인하세요.
500은 Apple 쪽 문제이므로 나중에 다시 시도하세요.
샌드박스에서 테스트하는 방법
이것을 테스트하는 일은 생각보다 간단하지만, 한 가지 주의할 점이 있습니다.
App Store Connect에 샌드박스 서버 URL을 입력하고, 샌드박스 Apple 계정으로 로그인한 뒤 무언가를 구매합니다. 그다음 앱의 환불 시트(beginRefundRequest)를 열고 요청을 제출합니다.
Apple의 환불 요청 테스트 가이드에는 테스트 환경이 어떻게 동작하는지 설명되어 있습니다. 어떤 사유든 선택하면 환불이 자동으로 승인되고, Other를 선택한 뒤 DECLINE을 입력하면 거부되며, GRANT_PRORATED를 입력하면 일부 환불(prorated)로 처리됩니다.
주의할 점은 바로 이 규칙에 있습니다. 샌드박스는 여러분의 서버가 보낸 데이터가 아니라 시트에 입력한 값을 기준으로 결정을 내리므로, 여러분의 데이터가 Apple을 실제로 설득하는지는 테스트로 알 수 없습니다.
테스트로 확인할 수 있는 것은 전체 배관(plumbing) 부분입니다. 알림이 도착하는지, api.storekit-sandbox.apple.com으로 보낸 PUT 요청이 202로 응답하는지, 그리고 REFUND 또는 REFUND_DECLINED가 도착했을 때 제대로 처리되는지입니다.
또한 샌드박스는 환불 이메일을 보내지 않으니, 이메일이 오기를 기다리지 마세요.
Apple 환불 자동화: 수동 작업을 멈춰야 할 때
수동으로 처리하면, 이 작업은 누군가가 하루 종일 알림을 지켜보다가 12시간 기한이 끝나기 전에 쿼리를 실행하고 다섯 개의 필드를 전송하는 일이 됩니다.
한 달에 Apple 환불 요청이 몇 건 정도라면 괜찮습니다. 하지만 아무도 확인하지 않는 긴 연휴가 처음 찾아오는 순간, 상황은 훨씬 나빠집니다.
Apple 환불 요청을 자동화한다는 것은 위의 모든 과정에서 사람을 중간 단계에서 빼내는 것입니다. 알림이 도착하면 여러분의 규칙이 배송 및 사용 데이터를 페이로드로 변환하고, 몇 분 안에 호출이 전송되며, 결정이 해당 케이스에 기록되고, REFUND가 발생하면 회수(revoke)가 트리거됩니다.
이를 구축하는 작업 자체는 그리 큰 프로젝트가 아닙니다. 조용히 비용을 계속 발생시키는 부분은, 이후 Apple의 변경 사항(예: V1에서 v2로의 전환)을 계속 따라가는 일입니다.
직접 구축하고 싶지 않다면, 이를 위해 만들어진 서비스들을 Apple 환불 관리 도구 비교에서 살펴본 적이 있습니다. Refund Sensor가 어디에 적합하고 어디에는 적합하지 않은지도 함께 다뤘습니다.
이 주제가 나올 때마다 제가 짚고 넘어가고 싶은 점이 하나 있습니다. Apple 환불 자동화는 누가 결정을 내리는지를 바꾸는 것이 아니라, 얼마나 안정적으로 응답하는지를 바꿀 뿐입니다. 이 API의 어떤 부분도 개발자에게 최종 결정권을 주지는 않습니다.
마무리
호출 자체는 간단합니다. 서명된 토큰 하나, PUT 요청 하나, 필드 다섯 개, 그리고 로그로 남길 202 응답.
성패를 가르는 것은 다른 곳에 있습니다. 이미 이용약관에 포함된 동의, 모든 구매에 설정된 appAccountToken, 몇 초 안에 조회할 수 있는 사용 기록, 그리고 알림이 울리는 새벽 3시에도 함께 깨어나는 무언가 말입니다.
이런 부분들을 제대로 정리해두면, 이 과정은 더 이상 허둥대는 일이 아니라 여러분의 서버가 당연히 처리하는 일이 됩니다.
자주 묻는 질문
CONSUMPTION_REQUEST에 응답할 때 사용하는 App Store Server API 엔드포인트입니다. 소비 정보를 담은 PUT 요청을 보내면 Apple이 202 Accepted를 반환합니다.
CONSUMPTION_REQUEST가 도착한 시점부터 12시간이 주어집니다. Apple은 이 동일한 12시간 이내에 업데이트된 페이로드를 제출하는 것도 허용합니다.
네. Apple은 12시간 이내에 업데이트된 페이로드를 보내는 것을 허용합니다. 첫 제출이 불완전했을 때 유용합니다.
아닙니다. 202 Accepted는 Apple이 정보를 수신했다는 의미일 뿐입니다. 결과는 이후 REFUND 또는 REFUND_DECLINED 알림으로 도착합니다.
네. 소비 정보를 제출할 때 customerConsented 필드는 필수입니다. 고객이 동의하지 않았다면, Apple 문서에서는 응답하지 말라고 안내하고 있습니다.



