Verifieer App Store server notifications voordat je op een terugbetaling reageert, want iedereen kan naar je webhook-URL posten
Een terugbetalingsmelding is gewoon een HTTPS-verzoek totdat je de handtekening controleert. Zo verifieer je App Store server notifications en Google Play RTDN-berichten, lees je wat elke store ondertekent en wat een vervalste of ten onrechte geweigerde terugbetaling je kost.

Belangrijkste inzichten
- Apple verstuurt elke App Store server notification van versie 2 als een signedPayload, een JSON Web Signature met drie Base64URL-delen: een header, een payload en een handtekening.
- De JWS-header van een App Store server notification bevat een x5c-certificaatketen van drie certificaten: een ondertekenend leaf-certificaat, een tussencertificaat van Apple met de Apple Worldwide Developer Relations-extensie 1.2.840.113635.100.6.2.1 en een rootcertificaat van Apple.
- De App Store Server Library van Apple verifieert meldingen met een SignedDataVerifier die de certificaatketen, de bundle-ID, de environment en in productie de appAppleId controleert, en die online kan nagaan of certificaten zijn ingetrokken.
- Google Play Real-time developer notifications worden niet door Google Play ondertekend. Bij een push-abonnement kan Pub/Sub een ondertekend OpenID Connect-token meesturen in de Authorization-header, waarvan je server de handtekening, de audience en de e-mailclaims controleert.
- Volgens Google meldt een RTDN alleen dat de status van een aankoop is gewijzigd. De veilige reactie op elke terugbetalingsmelding is dus om die te bevestigen met de Google Play Developer API voordat je toegang intrekt.
- Apple verstuurt App Store server notifications vanuit het subnet 17.0.0.0/8, maar een IP-allowlist beperkt alleen wie je endpoint kan bereiken. Pas de handtekening bewijst dat het bericht van Apple komt.
- Een verifier die echte berichten ten onrechte weigert, is net zo duur als een die vervalsingen accepteert, want voor een CONSUMPTION_REQUEST die je laat vallen geldt een antwoordtermijn van 12 uur en voor een Google Play-chargebackbeoordeling een termijn van 24 uur.
Iedereen die je webhook-URL kent, kan er een terugbetaling naartoe sturen. Dat is het ongemakkelijke feit achter elke App Store server notification en elke Google Play Real-time developer notification: zolang je het niet verifieert, is het bericht gewoon een HTTPS-POST met wat JSON erin. Als je server een abonnement intrekt, tegoed terugneemt of een account blokkeert omdat er in een verzoek REFUND stond, heb je die macht gegeven aan iedereen die een curl-commando kan schrijven.
Beide stores geven je een manier om dit te controleren. Apple ondertekent de melding zelf, zodat je App Store server notifications kunt verifiëren met niets anders dan het rootcertificaat van Apple. Google Play kiest een andere route. Het bericht is niet ondertekend, maar de aflevering via Pub/Sub kan dat wel zijn, en Google vraagt je hoe dan ook om elke wijziging via de API te bevestigen. Hieronder lees je wat elke aanpak bewijst en wat het kost als je je in de ene of de andere richting vergist.
Wat elke store werkelijk ondertekent
| Vraag | App Store Server Notifications V2 | Google Play RTDN |
|---|---|---|
| Is het bericht zelf ondertekend? | Ja, signedPayload is een JWS | Nee, het dataveld is base64-gecodeerde JSON |
| Wat de afzender bewijst | x5c-certificaatketen naar een root van Apple | Pub/Sub-OIDC-token bij push, of je eigen geauthenticeerde pull |
| Geneste ondertekende objecten | signedTransactionInfo en signedRenewalInfo, elk een eigen JWS | Geen |
| Velden die bij je app moeten passen | bundleId, appAppleId, environment | packageName |
| Bronnetwerk | 17.0.0.0/8, sandbox en productie | Google Cloud Pub/Sub |
| Bevestigen voordat je handelt | Optioneel, de ondertekende gegevens zijn het bewijs | Verplicht, Google zegt de Developer API aan te roepen |
| Transport | HTTPS, TLS 1.2 of hoger | HTTPS-push, of pull via clientbibliotheek |
Zo verifieer je App Store server notifications
De documentatie van Apple beschrijft de signedPayload als een string van drie Base64URL-gecodeerde onderdelen, gescheiden door punten: een header, een payload en een handtekening, ondertekend volgens de specificatie JSON Web Signature. De payload decoderen is eenvoudig. Hem vertrouwen vraagt vier controles.
- Lees de x5c-array uit de gedecodeerde header. Apple documenteert de volgorde: eerst het certificaat met de ondertekeningssleutel, dan een tussencertificaat van Apple, dan een rootcertificaat van Apple.
- Valideer die keten tegen een rootcertificaat van Apple dat je zelf hebt gedownload van de PKI-site van Apple, in de praktijk Apple Root CA - G3. Vertrouw nooit de root die in het bericht zelf meekwam, want een vervalser kan daar elk willekeurig certificaat plaatsen.
- Controleer of het tussencertificaat de Apple Worldwide Developer Relations-extensie 1.2.840.113635.100.6.2.1 bevat en verifieer daarna de JWS-handtekening met de publieke sleutel van het leaf-certificaat.
- Vergelijk de gedecodeerde gegevens met je app: bundleId, appAppleId in productie, en environment. Een geldige handtekening van Apple op de app van een andere ontwikkelaar, of op een sandbox-aankoop die je productie-endpoint bereikt, is nog altijd niet jouw terugbetaling.
Doe het daarna nog een keer voor de inhoud. Het data-object bevat signedTransactionInfo en signedRenewalInfo, en elk daarvan is een afzonderlijke JWS met een eigen x5c-keten. Een REFUND-handler die de envelop verifieert maar de transactie ongecontroleerd uitleest, laat in het midden een gat open.
Gebruik de bibliotheek van Apple in plaats van alles zelf te schrijven
Apple publiceert de App Store Server Library voor Swift, Java, Python en Node. De SignedDataVerifier daarvan neemt je rootcertificaten, een vlag voor online controles, de environment, je bundle-ID en, voor productie, je appAppleId. De Node-broncode laat zien wat hij afdwingt: precies drie certificaten in de keten, de Apple-leaf-extensie 1.2.840.113635.100.6.11.1 en de extensie van het tussencertificaat, de geldigheidsdata van de certificaten, en een overeenkomende bundle-ID, app-ID en environment. Met online controles ingeschakeld vraagt hij bovendien aan de OCSP-responder of het leaf- en het tussencertificaat zijn ingetrokken.

Is een IP-allowlist genoeg?
Nee. Apple zegt dat je het subnet 17.0.0.0/8 moet toevoegen als je server een allowlist nodig heeft, en dat geldt voor zowel sandbox als productie. Dat vermindert ruis. Het bewijst niet dat een bericht echt is, en het werkt niet meer zodra het netwerk van Apple verandert op een manier waar je niets over hebt gehoord. Houd de handtekeningcontrole als de echte poort.
Zo verifieer je terugbetalingsmeldingen van Google Play
Google Play publiceert RTDN-berichten naar een Pub/Sub-topic dat van jou is, en alleen het serviceaccount google-play-developer-notifications@system.gserviceaccount.com mag daarop de rol Pub/Sub Publisher hebben. Je abonnement levert de berichten vervolgens op een van twee manieren af, en elke manier heeft een eigen bewijs.
- Pull-abonnement: je server opent een geauthenticeerde verbinding met Pub/Sub en haalt de berichten op. Er is geen openbaar endpoint, dus er is niets waar een buitenstaander naartoe kan posten.
- Push-abonnement met authenticatie ingeschakeld: Pub/Sub ondertekent een JSON Web Token en stuurt het in de Authorization-header mee als Bearer-token. De handleiding van Google zegt dat je de handtekening van het token moet valideren en moet controleren of de e-mail- en audienceclaims overeenkomen met de waarden die je hebt ingesteld, en de voorbeeldcode vereist bovendien dat email_verified true is. Het token kan tot een uur oud zijn.
- Push-abonnement zonder authenticatie: alles wat de URL kan bereiken, kan een bericht sturen dat eruitziet als een RTDN.
Waarom Google wil dat je via de API bevestigt
Zelfs een perfect geauthenticeerde RTDN vertelt niet het hele verhaal. De installatiehandleiding van Google zegt duidelijk dat deze meldingen je alleen laten weten dat de status van een aankoop is gewijzigd, en dat je de Google Play Developer API moet aanroepen om de volledige status te krijgen. Bij een voidedPurchaseNotification betekent dat: de aankoop opzoeken in de Voided Purchases API, of de actuele status ervan ophalen, voordat je iets intrekt. De API-aanroep wordt geauthenticeerd met je eigen serviceaccount, dus het antwoord kan een vervalser niet nabootsen.
Wat een vervalste of geweigerde terugbetaling kost in geld
Een vervalste terugbetalingsmelding die je vertrouwt, kost je een betalende klant. Je sluit iemand buiten van wie je het geld nog hebt, die persoon opent een supportticket, en sommigen gaan naar de store en vragen de terugbetaling aan die je systeem al had gesimuleerd. Een vervalste aankoop of verlenging kost je in de andere richting: je levert GPU-rekenkracht, betaalde API-aanroepen bij derden, opslag en uitbetalingen aan makers tegen omzet die nooit heeft bestaan.
De omgekeerde fout is stiller en vaak groter. Een verifier die echte berichten weigert, door een verkeerde appAppleId, een sandbox-instelling in productie of een verouderd rootcertificaat, laat precies de twee terugbetalingsprocessen vallen waarin jouw bewijs telt. Apple vraagt om verbruiksgegevens binnen 12 uur na een CONSUMPTION_REQUEST. Google geeft je 24 uur om een chargebackbeoordeling te beantwoorden via orders.reviewrefund, en voor bestellingen geplaatst na 3 augustus 2026 kost een verloren Google Play-chargeback je de aankoopprijs minus de servicekosten van Play, plus de kosten die de bank van de koper in rekening brengt.
| Fout | Wat er gebeurt | Wat het kost |
|---|---|---|
| Vervalste REFUND geaccepteerd | Een betalende klant verliest toegang | Supporttijd en een echt terugbetalingsverzoek dat je zelf hebt uitgelokt |
| Vervalste aankoop geaccepteerd | Gratis toegang tot betaalde functies | Rekenkracht, API-aanroepen, opslag en uitbetalingen zonder omzet |
| Echte CONSUMPTION_REQUEST geweigerd | Apple beslist zonder jouw gegevens | De verkoop, plus alles wat al is uitgegeven om die te leveren |
| Echte chargebackbeoordeling geweigerd | Google beslist zonder jouw bewijs | Aankoopprijs en bankkosten bij bestellingen na 3 augustus 2026 |
Laat een verificatiebug geen deadline opeten
- Sla het ruwe verzoek op voordat je het verifieert, zodat een geweigerd bericht nooit echt verloren gaat.
- Stel een melding in bij elke mislukte verificatie. Eén vervalst bericht is ruis. Als alle berichten tegelijk mislukken, is het je eigen bug.
- Bij Apple kun je opnieuw afspelen wat je hebt geweigerd. Get Notification History geeft de oorspronkelijke signedPayload terug voor de afgelopen 180 dagen, dus herstel de verifier en haal de opgeslagen of herstelde payloads er opnieuw doorheen.
- Bij Google bestaat een openstaande chargebackbeoordeling alleen in de bijbehorende melding, dus een verkeerd ingestelde push-audience betekent een gemiste beoordeling. Test het na elke wijziging met Send Test Message in Play Console.
RefundHalt verifieert de meldingen van beide stores, bevestigt terugbetalingen via de store-API's voordat er iets verandert, en beantwoordt CONSUMPTION_REQUEST en chargebackbeoordelingen binnen hun termijnen. Zo kan een nepbericht je geen klant kosten en kan een echt bericht niet door de mazen glippen.
Veelgestelde vragen
- Hoe verifieer ik een App Store server notification?
- Verifieer de signedPayload als een JSON Web Signature. Lees de x5c-certificaatketen uit de header, valideer die tegen een rootcertificaat van Apple dat je van de PKI-site van Apple hebt gedownload, verifieer de handtekening met de sleutel van het leaf-certificaat en controleer daarna of bundleId, appAppleId en environment overeenkomen met je app. De App Store Server Library van Apple doet dit allemaal in de SignedDataVerifier.
- Welke sleutel gebruik ik om App Store server notifications te valideren?
- Je hebt geen sleutel uit App Store Connect nodig. De publieke sleutel zit in het eerste certificaat van de x5c-keten in de JWS-header, en je vertrouwt die pas nadat je de keten hebt gevalideerd tot aan een rootcertificaat van Apple dat je zelf hebt verkregen. Je sleutel voor in-app-aankopen is bedoeld om de API's van Apple aan te roepen, niet om meldingen te controleren.
- Zijn Google Play Real-time developer notifications ondertekend?
- Nee, Google Play ondertekent het RTDN-bericht zelf niet. Bij een geauthenticeerd push-abonnement stuurt Pub/Sub een ondertekend OpenID Connect-token in de Authorization-header dat jij valideert, en Google zegt dat je na elke melding de Google Play Developer API moet aanroepen om de volledige status van de aankoop te krijgen.
- Moet mijn server een fout teruggeven als een melding niet door de verificatie komt?
- Sla eerst het ruwe verzoek op en beslis dan. Apple probeert een melding van versie 2 opnieuw na een 40x- of 50x-antwoord, op 1, 12, 24, 48 en 72 uur na de vorige poging. Ligt de fout aan je eigen configuratie, dan kun je met Get Notification History de oorspronkelijke signedPayload 180 dagen lang terughalen en na de reparatie opnieuw verifiëren.
- Moet ik meldingen nog steeds verifiëren als ik alleen het IP-bereik van Apple toelaat?
- Ja. Apple vraagt servers met een allowlist om het subnet 17.0.0.0/8 toe te laten. Dat beperkt wie verbinding kan maken, maar bewijst niet dat een bericht echt is. De JWS-handtekening is de controle die een melding aan Apple en aan je app koppelt.
Bronnen en verder lezen
- Apple Developer: signedPayload
- Apple Developer: JWSDecodedHeader
- Apple Developer: Enabling App Store Server Notifications
- Apple Developer: Responding to App Store Server Notifications
- Apple on GitHub: App Store Server Library (Node)
- Android Developers: Get ready (Real-time developer notifications setup)
- Google Cloud: Authentication for push subscriptions
- Play Console Help: Updates to refund protection and chargeback cost responsibility
RefundHalt
De terugbetalingsautopiloot voor de App Store en Google Play
Lees verder
Buitensporige terugbetalingsverzoeken kunnen je meer kosten dan de terugbetalingen zelf, want beide app stores zien ze als kwaliteitssignaal
Apple's richtlijn 5.6.4 noemt buitensporige terugbetalingsverzoeken een teken dat een app niet aan de kwaliteitsnormen voldoet, en Google's distributieovereenkomst laat Google apps van nieuwe ontwikkelaars verwijderen op basis van het terugbetalingspercentage. Dit zegt elke store, dit kost een verwijdering en zo blijf je buiten schot.
Gemiste terugbetalingsmeldingen kunnen je de enige terugbetalingen kosten die je mag aanvechten, en beide stores laten je ze terughalen als je op tijd kijkt
Mist je server een terugbetalingsmelding, dan sluiten de twee terugbetalingsprocessen die om jouw bewijs vragen zonder jou. Zo herhalen Apple en Google Play de levering, zo ver terug kun je gemiste terugbetalingsmeldingen herstellen, en hier zitten de gaten.