메뉴 이름은 영어 App Store Connect 기준입니다. 한국어로 쓰면 번역된 이름으로 보입니다.
Apple이 보내는 것과 보내지 않는 것
Apple은 App Store Server Notifications를 “인앱 구매 이벤트를 실시간으로 알려 주는 서버 간(server-to-server) 서비스”라고 설명합니다. 이벤트가 생기면 App Store가 내가 정한 URL로 HTTP POST를 보냅니다. JSON 본문에는 signedPayload 필드 하나가 있고, 디코딩하면 notificationType, 대개 subtype, 그리고 서명된 거래 정보가 들어 있습니다.
돈이 들어오거나 나가는 유형:
| notificationType | 보내는 때 |
|---|---|
SUBSCRIBED | 처음 구독(INITIAL_BUY)하거나 다시 구독(RESUBSCRIBE)할 때. 오퍼 코드로 구독해도 이 유형입니다 |
DID_RENEW | 구독이 갱신될 때. 실패했던 갱신이 나중에 성공하면 subtype이 BILLING_RECOVERY |
ONE_TIME_CHARGE | 소모성·비소모성·비갱신 구독 상품을 구매(또는 오퍼 코드로 사용)할 때 |
OFFER_REDEEMED | 구독 중인 고객이 오퍼를 사용할 때 |
REFUND | Apple이 거래를 환불했을 때 |
REFUND_REVERSED | 분쟁 뒤 Apple이 환불을 취소했을 때 |
금액은 서명된 거래(JWSTransactionDecodedPayload)에 있습니다. price는 1/1000 단위(milliunits)라서 $1.99 구매는 1990으로 옵니다. 그 밖에 currency, storefront, productId, transactionId, purchaseDate, 오퍼가 적용됐다면 offerType이 있습니다.
만들기 전에 알아 둘 한계 네 가지:
- 인앱 구매만 옵니다. 이 서비스는 인앱 구매 이벤트를 다룹니다. 유료 앱 다운로드는 인앱 구매가 아니어서 알림이 오지 않고, App Store Connect 보고서에서만 보입니다.
- 수익금이 아니라 가격입니다.
price는 App Store Connect에 설정한 가격을 구매일 기준으로 기록한 값이며, 구독 오퍼 할인이 반영되고 소모성 상품은 수량만큼 곱해집니다. 실제로 받을 돈이 아닙니다. Apple도 재무·회계 목적에는 App Store Connect 보고서를 쓰라고 합니다. 판매 국가는currency가 아니라storefront로 판단하세요. - 가족 공유는 판매가 아닙니다. 가족 구성원이 가족 공유로 접근 권한을 얻을 때도
SUBSCRIBED와ONE_TIME_CHARGE가 옵니다. 이때 거래의inAppOwnershipType은FAMILY_SHARED이고, 구매자 본인은PURCHASED입니다. - 환경마다 URL 하나. 앱마다 프로덕션 URL 하나, 샌드박스 URL 하나만 둘 수 있습니다(2단계).
1단계: HTTPS 엔드포인트
엔드포인트 조건:
- TLS 1.2 이상의 HTTPS. URL에 포트를 적는다면
443이거나1024이상이어야 합니다. - 알림을 처리했으면 HTTP
200–206으로, Apple이 다시 보내야 하면40x나50x로 응답합니다. - 방화벽에서 허용 목록을 쓴다면 Apple의
17.0.0.0/8대역을 허용합니다.
성공 응답을 받지 못하면 Apple은 V2 알림을 다섯 번 다시 보냅니다. 직전 시도에서 1, 12, 24, 48, 72시간 뒤입니다. 재시도는 프로덕션에서만 하고, 샌드박스는 한 번만 보냅니다. 오래 멈췄다면 App Store Server API의 Get Notification History로 놓친 V2 알림을 받을 수 있습니다.
서버리스 함수(Cloudflare Workers, AWS Lambda, Google Cloud Run 등)면 충분합니다. 알림 하나가 작은 POST 한 번입니다.
2단계: App Store Connect에 URL 입력
필요한 역할: Account Holder, Admin, App Manager, Marketing 중 하나.
- Apps에서 앱을 고릅니다.
- 사이드바의 General 아래에서 App Information을 누릅니다.
- App Store Server Notifications까지 내려갑니다.
- Production Server URL에서 Set Up URL을 누르고, URL을 입력하고, Version 2를 고른 뒤 Save를 누릅니다.
- Sandbox Server URL도 같은 방법으로 입력합니다. 두 환경에 같은 URL을 써도 됩니다.
Apple 도움말의 두 가지 주의점: 샌드박스 URL을 넣지 않으면 두 환경의 알림이 모두 프로덕션 URL로 가고, 샌드박스 URL만 넣으면 프로덕션 알림은 오지 않습니다.
이미 구독 관리 서비스를 쓰고 있다면? RevenueCat 같은 서비스나 직접 만든 백엔드가 이미 URL을 쓰고 있다면 덮어쓰지 마세요. 환경마다 URL은 딱 하나입니다. 대신 그 URL을 가진 서버가 내 서버로 알림을 전달하게 하세요.
같은 설정은 App Store Connect API의 App 리소스에도 있습니다(subscriptionStatusUrl, subscriptionStatusUrlVersion과 …ForSandbox). 도구들이 API 키로 이 값을 설정하는 방식입니다.
3단계: 서명 검증과 디코딩
알림 안의 모든 것은 JWS(JSON Web Signature)입니다. 바깥의 signedPayload, 그 안의 data.signedTransactionInfo, 구독이라면 data.signedRenewalInfo까지 각각 서명을 확인한 뒤에 믿어야 합니다. JWS 헤더의 x5c에는 Apple이 서명에 쓴 인증서 체인(서명 인증서, Apple 중간 인증서, Apple PKI의 루트 인증서)이 들어 있습니다.
직접 구현할 필요는 없습니다. Apple의 오픈소스 App Store Server Library(Swift, Java, Python, Node)가 알림·거래·갱신 정보를 검증하고 디코딩해 줍니다. Node 예시:
import { SignedDataVerifier, Environment } from "@apple/app-store-server-library";
// Apple 루트 인증서: https://www.apple.com/certificateauthority/
// 환경마다 verifier를 하나씩. Production이면 appAppleId가 필요합니다.
const verifier = new SignedDataVerifier(appleRootCAs, true, Environment.PRODUCTION, "com.example.app", 1234567890);
export async function onNotification(request) {
const { signedPayload } = await request.json();
const notification = await verifier.verifyAndDecodeNotification(signedPayload);
const signedTransaction = notification.data?.signedTransactionInfo;
const transaction = signedTransaction ? await verifier.verifyAndDecodeTransaction(signedTransaction) : null;
// notification.notificationType, notification.subtype, notification.notificationUUID
// transaction.price / 1000, transaction.currency, transaction.storefront, transaction.productId
return new Response(null, { status: 200 });
}
4단계: 테스트 알림 보내기
App Store Server API의 Request a Test Notification 엔드포인트를 부릅니다(In-App Purchase 키로 만든 JWT로 인증). 프로덕션 엔드포인트를 부르면 프로덕션 URL로, 샌드박스 엔드포인트를 부르면 샌드박스 URL로 TEST 알림이 옵니다. Get Test Notification Status로 내 서버가 어떻게 응답했는지 확인할 수 있습니다.
실제 구매 유형을 보려면 Sandbox Apple Account로 샌드박스에서 구매해 보세요. 구독을 사면 SUBSCRIBED(subtype INITIAL_BUY)가 옵니다.
5단계: 알림을 판매 알림으로
SUBSCRIBED,DID_RENEW,ONE_TIME_CHARGE,OFFER_REDEEMED는 수입(거래당 한 번 — 다음 항목 참고),REFUND는 마이너스,REFUND_REVERSED는 다시 수입으로 셉니다.- 판매는
transactionId를 기준으로 셉니다. 여러 알림이 같은 거래를 담을 수 있습니다(예: 구독 중인 고객의OFFER_REDEEMED).notificationUUID는 다시 전달된 알림을 알아보는 값일 뿐입니다. - 가족 공유 접근은 건너뜁니다.
inAppOwnershipType이FAMILY_SHARED인 거래는 무시하세요. - 샌드박스는 합계에서 뺍니다. 디코딩한 데이터에
environment가 들어 있습니다. price / 1000을currency로 보여 주고,storefront로 묶습니다.
그다음은 원하는 곳으로 보내면 됩니다. Slack·Discord 웹훅, 푸시 알림, 메뉴바 앱 등.
서버를 운영하고 싶지 않다면
RevBell은 Mac에서 1–5단계를 대신합니다.
- 이미 입력한 App Store Connect API 키(내 Mac의 키체인에만 보관)로, 체크한 앱의 알림 URL만 설정합니다. URL이 RevenueCat이나 다른 서버를 가리키는 앱은 체크가 해제된 채 경고와 함께 보여서, 모르는 사이에 바뀌는 일이 없습니다.
- RevBell 서버는 Apple 서명을 검증하고 거래 식별자, 상품, 가격, 통화, 스토어프런트, 날짜, 환경만 45일 동안 보관합니다. 원본 알림이나 app account token은 저장하지 않습니다.
- 판매가 들어오면 Mac에 알리고 노치에 보여 줍니다. 보통 1–2분 안에 도착합니다. 판매 상세(앱·금액·상품·국가)는 RevBell Pro 기능이고, 무료 플랜은 판매가 발생했다는 사실을 알려 줍니다.
자주 묻는 질문
App Store Connect 판매 보고서를 대신하나요?
아니요. 알림은 빨리 알려 주는 용도이고, 수익금·세금·환율이 반영된 재무 기록은 보고서입니다.
유료 앱 다운로드도 알림이 오나요?
아니요. 인앱 구매 이벤트만 옵니다.
서버가 멈추면요?
프로덕션 알림은 약 6.5일(1+12+24+48+72시간)에 걸쳐 다섯 번 재시도됩니다. 그 뒤에는 Get Notification History를 쓰세요.
두 서비스가 같은 알림을 받을 수 있나요?
직접은 안 됩니다. 앱마다 프로덕션 URL과 샌드박스 URL이 하나씩입니다. URL을 가진 서버에서 전달하세요.
얼마나 빠른가요?
Apple은 실시간 서비스라고 설명하지만 전달 시간을 공개하지는 않습니다. RevBell 서버를 거쳐 Mac까지 오는 판매 알림은 보통 구매 후 1–2분 안에 도착합니다.