In-app purchase onboarding (Apple + Google)
This guide walks you through the provider-side configuration needed for Paylera to receive purchase notifications from the Apple App Store and Google Play. Budget 30–60 minutes per provider the first time you do it — most of that is clicking through Apple and Google’s consoles, not anything on the Paylera side.
By the end you’ll have:
- Apple App Store Server Notifications V2 pointing at Paylera’s ingress, plus an App Store Connect API key uploaded.
- Google Play Real-time Developer Notifications publishing to a Pub/Sub subscription that Paylera polls, plus a service-account JSON uploaded.
- A “Validate” check that confirms both sides round-trip a fixture notification end-to-end.
If you’re integrating Stripe or Toss instead of (or in addition to) the app stores, those flow through Paylera’s primary payment-provider configuration — see the Provider setup overview for that path. IAP and card/checkout providers can coexist on the same tenant.
Before you start
Have these ready:
- Apple side: Admin access to App Store Connect, plus your iOS app’s Bundle ID(s) for both sandbox and production.
- Google side: Owner access to the Play Console for the app, plus Owner or sufficient IAM on a Google Cloud project where you can create service accounts and Pub/Sub topics.
- Paylera side: a Paylera tenant (sandbox is fine to start) and Owner or Admin role on it.
You do not need to redeploy your mobile app to complete this onboarding — the provider-side notifications flow server-to-server.
Part 1 — Apple App Store Connect setup
Apple’s “App Store Server Notifications V2” (ASSN V2) is a webhook that Apple POSTs to a URL you configure. Each notification is a JWS signed by an Apple-issued cert. To validate purchases beyond the notification itself (e.g. fetch transaction history) you also need an App Store Connect API key.
1.1 Generate an App Store Connect API key
-
Sign in to App Store Connect (https://appstoreconnect.apple.com).
-
Go to Users and Access → Integrations → App Store Connect API → Keys.
-
Click Generate API Key (or the + button if you’ve created keys before).
-
Name it something recognizable — we recommend
paylera-iap-{environment}. -
Under Access, choose the Admin role.
<screenshot: App Store Connect “Generate API Key” dialog with role selector visible>
-
Click Generate. Apple shows the key once — download the
.p8file immediately. You cannot re-download it. -
On the same page, note three values you’ll need shortly:
- Key ID — shown in the row for the key you just created (10-character alphanumeric).
- Issuer ID — shown at the top of the Keys page (UUID).
- Team ID — Apple Developer Account → Membership → Team ID (10-character alphanumeric).
1.2 Find your Bundle IDs
In App Store Connect, open your app and note the Bundle ID under App Information. If you ship a separate sandbox/TestFlight build under a different Bundle ID, note that one too — Paylera treats them independently so sandbox traffic doesn’t pollute production aggregates.
1.3 Enable App Store Server Notifications V2
-
In App Store Connect, open your app.
-
Go to App Information → App Store Server Notifications → Production Server URL (and Sandbox Server URL separately).
-
Set both URLs to Paylera’s ingress:
Production: https://api.paylera.dev/v1/webhooks/apple_app_storeSandbox: https://api.paylera.dev/v1/webhooks/apple_app_store(Apple separates sandbox and production by which environment field is on the notification payload, not by URL.)
If you operate your own relay (e.g. you want to MITM notifications through your infra first), use your relay URL here and forward to Paylera; see the Relay protocol guide for the format Paylera expects when notifications arrive via a relay.
-
Under Version, choose Version 2. V1 is unsupported.
<screenshot: App Store Connect ASSN V2 configuration with Production / Sandbox URL fields and Version 2 selected>
-
Click Save.
1.4 Upload to Paylera
-
In the Paylera tenant portal, go to Settings → IAP Credentials.
-
Under Apple App Store, click Add credential.
-
Fill in the form:
Field Value Key ID The 10-char ID from step 1.1 Issuer ID The UUID from step 1.1 Team ID The 10-char ID from step 1.1 Bundle ID (production) From step 1.2 Bundle ID (sandbox) From step 1.2, if separate .p8key fileDrag-and-drop the .p8you downloadedThe portal validates the
.p8parses as a valid EC private key at upload time. A malformed file is rejected here, not later when Apple sends the first notification. -
Click Save. The credential row appears with a green status dot and a Validate button — see §4.
Part 2 — Google Play setup
Google Play uses a more involved path: notifications publish to a
Pub/Sub topic in your GCP project, Paylera subscribes to that
topic via a service account you provision. The flow is
Play Console → Pub/Sub topic → Paylera’s pubsub-ingress.
2.1 Create or pick a GCP project
You can reuse an existing project; nothing about IAP requires a dedicated one. The service account you create later will be scoped to just what Paylera needs, so blast radius is bounded regardless.
gcloud projects create paylera-iap-relay # if you want a fresh one2.2 Enable the Play Developer API
In the GCP console (or via gcloud):
gcloud services enable androidpublisher.googleapis.com \ pubsub.googleapis.com \ --project=<project>The Pub/Sub API is for the notifications transport; the Android Publisher API is for Paylera’s polling fallback when it needs to confirm a purchase against Google’s source of truth.
2.3 Create a service account
-
In the GCP console, go to IAM & Admin → Service Accounts.
-
Create service account, name it
paylera-iap. -
Grant the service account these roles:
- Pub/Sub Subscriber on the project (or just on the subscription you’ll create in step 2.5).
- No other roles at the project level.
-
After the account is created, click into it → Keys → Add key → Create new key → JSON. Download
service_account.json.<screenshot: GCP service account Keys tab with “Create new key” highlighted>
Store it somewhere short-term; you’ll upload it to Paylera in step 2.7 and then delete the local copy.
2.4 Grant the service account Play Console access
- Open Play Console (https://play.google.com/console).
- Settings → Developer account → Users and permissions.
- Invite new users, enter the service account’s email
(looks like
paylera-iap@<project>.iam.gserviceaccount.com). - Grant View financial data and Manage orders and subscriptions at the app level for the app(s) you want Paylera to see.
- Save and accept the invite (the service account auto-accepts within a few minutes).
2.5 Create the Pub/Sub topic + subscription
In the GCP console:
- Pub/Sub → Topics → Create topic, name it
play-rtdn-<app-package-name>. - Subscriptions → Create subscription, name it
paylera-<app-package-name>. Set the topic from step 1, and Delivery type: Pull. Leave the rest at defaults. - Note both the topic name (full
projects/<project>/topics/...) and the subscription name (fullprojects/<project>/subscriptions/...).
2.6 Tell Play Console to publish to the topic
-
In Play Console, open your app.
-
Monetize → Monetization setup → Real-time developer notifications.
-
Paste the topic name from step 2.5 into the Topic name field.
-
Click Send test notification — Play sends a
testNotificationenvelope to the topic. You can check Pub/Sub → Topics →→ Messages in the GCP console to see it land. (Paylera will also receive it once you’ve completed step 2.7; we deduplicate test envelopes silently.)<screenshot: Play Console RTDN configuration with topic name field and Send test notification button>
2.7 Upload to Paylera
-
In the Paylera tenant portal, go to Settings → IAP Credentials.
-
Under Google Play, click Add credential.
-
Fill in the form:
Field Value Package name Your app’s package name (e.g. com.example.app)Pub/Sub subscription Full subscription name from step 2.5 service_account.jsonDrag-and-drop the JSON from step 2.3 The portal validates
service_account.jsonparses and contains"type": "service_account"at upload time. If you accidentally uploaded a user-account credential or a malformed file, you’ll see an error here. -
Click Save. Paylera’s
pubsub-ingresspicks up the new credential on its next sweep (≤30 seconds) and spins up a subscriber goroutine for the subscription.
Part 3 — Stripe and Toss (brief)
Stripe (cards / SEPA / wallets) and Toss (Korean card and KakaoPay) are configured through Paylera’s primary payment-provider section, not under IAP Credentials.
- Stripe: Settings → Payment providers → Stripe → Connect. Follow the Stripe OAuth flow; Paylera handles webhook registration on its own ingress.
- Toss: Settings → Payment providers → Toss → upload your Toss client key + secret key from Toss Payments dashboard.
Both providers run in parallel with IAP — a tenant can take
in-app purchases through Apple/Google and card payments
through Stripe with the same Paylera setup. Revenue from each
provider lands in revenue_events tagged with the source, and
the dashboard splits totals by source.
Part 4 — Testing your setup
After both Part 1 and Part 2 are complete, you have two ways to verify the wiring before you cut over real traffic.
4.1 The “Validate” button in the portal
In Settings → IAP Credentials, each credential row has a Validate button. Clicking it runs a synchronous check:
- Apple: Paylera mints a JWT against your uploaded
.p8, calls Apple’s App Store Server APIGET /inApps/v1/lookup/<a known-bad order ID>, and confirms Apple returns a 404 (not a 401). A 404 means “we recognize your key but no such order exists” — exactly what we want. A 401 means the key/team/issuer triple is wrong. - Google: Paylera calls
subscriptions.acknowledgeon a known-nonexistent subscription ID. Google returns 404 if the service account works and has the right Play Console scopes; 403 if the service-account email isn’t bound in Play Console; 401 if the JSON is wrong.
Validation runs in <2 seconds and reports the specific failure mode if anything is off.
4.2 End-to-end fixture purchase
Even after Validate passes, a real purchase exercises the full notification path. Both providers have sandbox/test rails for this.
- Apple sandbox: in App Store Connect, create a Sandbox Tester under Users and Access → Sandbox Testers. Sign your test device into that account and make a sandbox purchase. Apple emits ASSN V2 within ~30 seconds.
- Google internal-test track: upload an internal test build to Play, add your tester email to the internal-test track, install via the test link, and make a purchase with a test card (or use a real card under Google Play license testing — Google won’t charge accounts on the license-testers list).
Within ~1 minute of the purchase, the event should appear in Paylera’s Revenue dashboard under the sandbox environment filter. If it doesn’t, check Troubleshooting below or refer to the operator revenue-ingest-stuck runbook.
Troubleshooting
Apple notifications return 401 at ingress
Most often a Bundle ID mismatch: Apple’s notification carries
the Bundle ID it was issued against, and Paylera rejects
notifications for Bundle IDs that aren’t registered on the
tenant. Double-check that both your production and sandbox Bundle
IDs are listed on the credential row in Settings → IAP
Credentials. Sandbox builds in particular often have a .dev or
.sandbox suffix.
Less commonly, the Apple notification’s signature doesn’t chain to a root Paylera recognizes (Apple rotated their CA). The ingress falls back to a live root fetch automatically; if you’re seeing 401s for >10 minutes, page on-call (see the revenue-ingest-stuck runbook — this is their problem, not yours).
.p8 upload rejected by the portal
The portal parses the .p8 as an EC private key before accepting
it. Common causes of rejection:
- You uploaded the certificate (
.cer) instead of the private key (.p8). - The
.p8was opened in a text editor and re-saved with a BOM or CRLF line endings. Re-download from App Store Connect and upload directly without opening.
You only get one shot to download the .p8 from Apple — if you
lost it, you have to revoke the key in App Store Connect and
generate a fresh one.
Google service account returns 403
The service account exists and the JSON is valid, but Google rejects calls with “The current user has insufficient permissions.” This means step 2.4 didn’t take effect:
- Open Play Console → Users and permissions.
- Confirm the service account email is listed.
- Confirm it has View financial data and Manage orders and subscriptions for the specific app, not just the account-level default.
- Wait ~5 minutes after granting permissions for Google to propagate. Then click Validate again.
Pub/Sub subscription returns “permission denied”
Either the service account doesn’t have Pub/Sub Subscriber on the subscription, or the subscription name in the Paylera portal doesn’t match the actual GCP resource path.
The format Paylera expects is the full path:
projects/<project>/subscriptions/<sub-name>. If you pasted
just the short name, edit the credential and add the full path.
Test notification arrived at Pub/Sub but never appeared in Paylera
Two possibilities:
- The subscription is the wrong type. Paylera pulls; if you created a push subscription instead, no one is pulling. Recreate as a pull subscription.
pubsub-ingressis at its per-instance tenant cap and hasn’t rotated your tenant onto a fresh replica yet. This auto-resolves within 30 seconds; if not, contact support.
What happens next
Once Validate passes and a fixture purchase round-trips:
- Real-money purchases will start landing in
revenue_eventsas soon as Apple/Google route them. - They’ll roll up into
revenue_aggregateswithin 5 minutes. - The Revenue dashboard (and the
useRevenue()React hook) will reflect them on the next render.
For the operational side — what to do if revenue stops flowing,
how Paylera handles Apple’s CA rotations, what backfills look
like — see the operator runbooks under
docs/ops/runbooks/.
You don’t need to learn any of that to run normally; it’s there
for the on-call rotation if something breaks.