Усі статті
Детальний розбір8 хв читання

Перевіряйте серверні сповіщення App Store, перш ніж діяти щодо повернення коштів, бо надіслати запит на ваш webhook URL може будь-хто

Сповіщення про повернення коштів залишається звичайним HTTPS-запитом, доки ви не перевірите його підпис. Пояснюємо, як перевіряти серверні сповіщення App Store і повідомлення Google Play RTDN, що саме підписує кожен магазин і скільки коштує підроблене або помилково відхилене повернення.

Латунна печатка для сургучу та запечатаний конверт поруч із ноутбуком на столі в теплому світлі лампи, образ того, чому серверні сповіщення App Store варто перевіряти, перш ніж їм довіряти

Головне

  • Apple надсилає кожне серверне сповіщення App Store версії 2 як signedPayload, тобто JSON Web Signature із трьох частин у Base64URL: заголовка, корисного навантаження та підпису.
  • Заголовок JWS серверного сповіщення App Store містить ланцюжок сертифікатів x5c із трьох сертифікатів: кінцевого сертифіката підпису, проміжного сертифіката Apple з розширенням Apple Worldwide Developer Relations 1.2.840.113635.100.6.2.1 і кореневого сертифіката Apple.
  • App Store Server Library від Apple перевіряє сповіщення за допомогою SignedDataVerifier, який перевіряє ланцюжок сертифікатів, bundle ID, середовище, а в production також appAppleId, і може перевіряти відкликання сертифікатів онлайн.
  • Google Play Real-time developer notifications не підписуються Google Play. У push-підписці Pub/Sub може додати підписаний токен OpenID Connect у заголовок Authorization, а ваш сервер перевіряє його підпис, а також claims audience та email.
  • Google зазначає, що RTDN лише повідомляє про зміну стану покупки, тож безпечна реакція на будь-яке сповіщення про повернення коштів полягає в тому, щоб підтвердити його через Google Play Developer API, перш ніж відкликати доступ.
  • Apple надсилає серверні сповіщення App Store з підмережі 17.0.0.0/8, але список дозволених IP лише звужує коло тих, хто може дістатися до вашого endpoint. Те, що повідомлення надійшло від Apple, доводить саме підпис.
  • Верифікатор, який помилково відхиляє справжні повідомлення, коштує так само дорого, як і той, що приймає підробки, бо на пропущений CONSUMPTION_REQUEST є 12 годин для відповіді, а на розгляд chargeback у Google Play є 24 години.

Будь-хто, хто дізнається ваш webhook URL, може надіслати на нього повернення коштів. Це незручна правда, що стоїть за кожним серверним сповіщенням App Store і кожним Google Play Real-time developer notification: доки ви його не перевірили, повідомлення є лише HTTPS POST із якимось JSON усередині. Якщо ваш сервер скасовує підписку, забирає кредити або блокує акаунт лише тому, що в запиті написано REFUND, ви передали цю владу кожному, хто вміє написати команду curl.

Обидва магазини дають вам спосіб перевірки. Apple підписує саме сповіщення, тож ви можете перевіряти серверні сповіщення App Store, маючи лише кореневий сертифікат Apple. Google Play іде іншим шляхом. Повідомлення не підписане, але доставку через Pub/Sub можна підписати, і Google у будь-якому разі радить підтверджувати кожну зміну через свій API. Нижче пояснюємо, що доводить кожен із варіантів і чого коштує помилка в будь-який бік.

Що насправді підписує кожен магазин

ПитанняApp Store Server Notifications V2Google Play RTDN
Чи підписане саме повідомлення?Так, signedPayload є JWSНі, поле data містить JSON у кодуванні base64
Що доводить відправникаЛанцюжок сертифікатів x5c до кореня AppleТокен Pub/Sub OIDC у push або ваш власний автентифікований pull
Вкладені підписані об'єктиsignedTransactionInfo і signedRenewalInfo, кожен зі своїм JWSНемає
Поля для зіставлення з вашим застосункомbundleId, appAppleId, environmentpackageName
Мережа-джерело17.0.0.0/8, sandbox і productionGoogle Cloud Pub/Sub
Підтвердження перед дієюНеобов'язкове, підписані дані і є записомОбов'язкове, Google радить викликати Developer API
ТранспортHTTPS, TLS 1.2 або новішийHTTPS push або pull через клієнтську бібліотеку

Як перевіряти серверні сповіщення App Store

Документація Apple описує signedPayload як рядок із трьох компонентів у кодуванні Base64URL, розділених крапками: заголовка, корисного навантаження та підпису, підписаний відповідно до специфікації JSON Web Signature. Декодувати корисне навантаження легко. Щоб йому довіряти, потрібні чотири перевірки.

  • Прочитайте масив x5c із декодованого заголовка. Apple документує порядок: спершу сертифікат із ключем підпису, далі проміжний сертифікат Apple, потім кореневий сертифікат Apple.
  • Перевірте цей ланцюжок щодо кореневого сертифіката Apple, який ви самі завантажили з PKI-сайту Apple, на практиці це Apple Root CA - G3. Ніколи не довіряйте кореневому сертифікату, що прийшов усередині повідомлення, бо зловмисник може покласти туди будь-який сертифікат.
  • Переконайтеся, що проміжний сертифікат містить розширення Apple Worldwide Developer Relations 1.2.840.113635.100.6.2.1, а потім перевірте підпис JWS відкритим ключем кінцевого сертифіката.
  • Зіставте декодовані дані зі своїм застосунком: bundleId, appAppleId у production та environment. Дійсний підпис Apple на застосунку іншого розробника або на покупці з sandbox, що надійшла на ваш production endpoint, однаково не є вашим поверненням коштів.

Потім повторіть те саме для вмісту. Об'єкт data містить signedTransactionInfo і signedRenewalInfo, і кожен із них є окремим JWS зі своїм ланцюжком x5c. Обробник REFUND, який перевіряє конверт, але читає транзакцію без перевірки, залишив пролом посередині.

Використовуйте бібліотеку Apple замість власної реалізації

Apple публікує App Store Server Library для Swift, Java, Python і Node. Її SignedDataVerifier приймає ваші кореневі сертифікати, прапорець онлайн-перевірок, середовище, ваш bundle ID, а для production також ваш appAppleId. Код Node показує, що саме вона вимагає: рівно три сертифікати в ланцюжку, розширення кінцевого сертифіката Apple 1.2.840.113635.100.6.11.1 і розширення проміжного сертифіката, дати чинності сертифікатів, а також збіг bundle ID, app ID і середовища. Коли онлайн-перевірки ввімкнено, вона також запитує в OCSP responder, чи не відкликано кінцевий і проміжний сертифікати.

Ювелірна лупа над паперовим сертифікатом із тисненою печаткою на дерев'яному столі, саме така ретельна перевірка, якій варто піддати повідомлення про повернення коштів, перш ніж йому довіряти

Чи достатньо списку дозволених IP?

Ні. Apple радить додати підмережу 17.0.0.0/8, якщо вашому серверу потрібен список дозволених адрес, і це стосується як sandbox, так і production. Це зменшує шум. Але не доводить, що повідомлення справжнє, і перестає працювати, щойно мережа Apple зміниться так, що ви про це не дізнаєтеся. Справжнім бар'єром залишайте перевірку підпису.

Як перевіряти сповіщення про повернення коштів у Google Play

Google Play публікує повідомлення RTDN у ваш власний топік Pub/Sub, і роль Pub/Sub Publisher у ньому має бути лише в сервісного акаунта google-play-developer-notifications@system.gserviceaccount.com. Далі ваша підписка доставляє повідомлення одним із двох способів, і кожен має свій доказ.

  • Pull-підписка: ваш сервер відкриває автентифіковане з'єднання з Pub/Sub і отримує повідомлення. Публічного endpoint немає, тож стороннім нікуди надсилати запити.
  • Push-підписка з увімкненою автентифікацією: Pub/Sub підписує JSON Web Token і надсилає його в заголовку Authorization як Bearer-токен. Посібник Google радить перевірити підпис токена і переконатися, що його claims email та audience збігаються з налаштованими вами значеннями, а приклад коду також вимагає, щоб email_verified мав значення true. Токен може бути давністю до однієї години.
  • Push-підписка без автентифікації: будь-що, що може дістатися до URL, здатне надіслати повідомлення у формі RTDN.

Чому Google хоче, щоб ви підтверджували через API

Навіть ідеально автентифікований RTDN не розповідає всієї історії. Посібник Google із налаштування прямо каже, що ці сповіщення лише повідомляють про зміну стану покупки, і щоб отримати повний статус, потрібно викликати Google Play Developer API. Для voidedPurchaseNotification це означає знайти покупку у Voided Purchases API або отримати її поточний стан, перш ніж щось відкликати. Виклик API автентифікується вашим власним сервісним акаунтом, тож його відповідь зловмисник підробити не може.

Скільки коштує підроблене або відхилене повернення

Підроблене сповіщення про повернення коштів, якому ви повірили, коштує вам клієнта, що платить. Ви блокуєте людину, чиї гроші досі у вас, вона відкриває звернення до підтримки, а дехто піде в магазин і попросить те повернення, яке ваша система вже вдала, що надала. Підроблена покупка або продовження коштує в протилежний бік: ви надаєте обчислення на GPU, платні виклики сторонніх API, сховище й виплати авторам під дохід, якого ніколи не було.

Протилежна помилка тихіша і часто більша. Верифікатор, що відхиляє справжні повідомлення через неправильний appAppleId, налаштування sandbox у production або застарілий кореневий сертифікат, втрачає саме ті два процеси повернення коштів, де важать ваші докази. Apple просить дані про споживання протягом 12 годин після CONSUMPTION_REQUEST. Google дає 24 години, щоб відповісти на розгляд chargeback через orders.reviewrefund, а для замовлень, оформлених після August 3, 2026, програний chargeback у Google Play коштує вам ціну покупки за вирахуванням сервісного збору Play, плюс комісію, яку стягує банк покупця.

ЗбійЩо відбуваєтьсяСкільки це коштує
Прийнято підроблений REFUNDКлієнт, що платить, втрачає доступЧас підтримки і справжній запит на повернення, який ви самі спровокували
Прийнято підроблену покупкуБезкоштовний доступ до платних функційОбчислення, виклики API, сховище й виплати без доходу
Відхилено справжній CONSUMPTION_REQUESTApple вирішує без ваших данихПродаж і все, що вже витрачено на його виконання
Відхилено справжній розгляд chargebackGoogle вирішує без ваших доказівЦіна покупки й банківські комісії за замовлення після August 3, 2026

Не дайте помилці перевірки з'їсти дедлайн

  • Зберігайте сирий запит перед перевіркою, щоб відхилене повідомлення ніколи не губилося насправді.
  • Надсилайте сповіщення про кожен збій перевірки. Одне підроблене повідомлення є шумом. Коли збоять усі повідомлення одночасно, це ваша власна помилка.
  • В Apple можна повторно обробити те, що ви відхилили. Get Notification History повертає оригінальний signedPayload за останні 180 днів, тож виправте верифікатор і проженіть через нього збережені або відновлені payloads ще раз.
  • У Google розгляд chargeback, що очікує, існує лише у своєму сповіщенні, тож неправильно налаштований push audience означає пропущений розгляд. Перевіряйте це за допомогою Send Test Message у Play Console після кожної зміни.

RefundHalt перевіряє сповіщення обох магазинів, підтверджує повернення коштів через API магазинів, перш ніж щось змінити, і відповідає на CONSUMPTION_REQUEST та розгляди chargeback у їхні терміни, тож фальшиве повідомлення не коштуватиме вам клієнта, а справжнє не прослизне повз.

Поширені запитання

Як перевірити серверне сповіщення App Store?
Перевірте signedPayload як JSON Web Signature. Прочитайте ланцюжок сертифікатів x5c з його заголовка, перевірте його щодо кореневого сертифіката Apple, завантаженого з PKI-сайту Apple, перевірте підпис ключем кінцевого сертифіката, а потім переконайтеся, що bundleId, appAppleId та environment відповідають вашому застосунку. App Store Server Library від Apple робить усе це у своєму SignedDataVerifier.
Який ключ використовувати для перевірки серверних сповіщень App Store?
Ключ з App Store Connect вам не потрібен. Відкритий ключ міститься в першому сертифікаті ланцюжка x5c усередині заголовка JWS, і ви довіряєте йому лише після перевірки ланцюжка до кореневого сертифіката Apple, який отримали самостійно. Ваш ключ In-App Purchase призначений для виклику API Apple, а не для перевірки сповіщень.
Чи підписані Google Play Real-time developer notifications?
Ні, Google Play не підписує саме повідомлення RTDN. В автентифікованій push-підписці Pub/Sub надсилає підписаний токен OpenID Connect у заголовку Authorization, який ви перевіряєте, а Google радить після кожного сповіщення викликати Google Play Developer API, щоб отримати повний статус покупки.
Чи має мій сервер повертати помилку, коли сповіщення не пройшло перевірку?
Спершу збережіть сирий запит, а потім вирішуйте. Apple повторює сповіщення версії 2 після відповіді 40x або 50x через 1, 12, 24, 48 і 72 години після попередньої спроби. Якщо збій спричинено вашою власною конфігурацією, Get Notification History дає змогу відновити оригінальний signedPayload протягом 180 днів і повторно перевірити його після виправлення.
Чи потрібно перевіряти сповіщення, якщо я дозволяю лише діапазон IP Apple?
Так. Apple просить сервери зі списком дозволених адрес приймати підмережу 17.0.0.0/8, що обмежує коло тих, хто може під'єднатися, але не доводить, що повідомлення справжнє. Саме підпис JWS пов'язує сповіщення з Apple і з вашим застосунком.

Джерела та додаткове читання

RefundHalt

Автопілот повернень для App Store і Google Play

Читайте далі

Посібник8 хв читання

Надмірні запити на повернення коштують дорожче за самі повернення: обидва магазини застосунків вважають їх сигналом якості

Правило 5.6.4 Apple прямо називає надмірні запити на повернення ознакою того, що застосунок не відповідає стандартам якості, а угода Google про розповсюдження дозволяє видаляти застосунки нових розробників через частку повернень. Розбираємо, що пише кожен магазин, скільки коштує видалення і як не потрапити під удар.

Посібник9 хв читання

Пропущені сповіщення про повернення коштів можуть забрати у вас єдині повернення, які ви маєте право оскаржити, і обидва магазини дають змогу їх відновити, якщо перевірити вчасно

Коли ваш сервер пропускає сповіщення про повернення коштів, два процеси, у яких магазин просить ваші докази, закриваються без вас. Розповідаємо, як Apple і Google Play повторюють доставку, за який період можна відновити пропущені сповіщення про повернення та де в цьому відновленні прогалини.

Наступний запит на повернення вже в дорозі.

Налаштуйте RefundHalt за час, потрібний, щоб прочитати черговий лист у підтримку про повернення, яке ви не встигли оскаржити.