Tech 4 min read

Service Account Setup for Server-Side Google Play Billing Verification

In my previous article, Flutter WebView + Google Play Billing Implementation Guide, I got the Flutter-side purchase flow working. Now I want to verify receipts on the server side.

To prevent fraudulent purchases, you need to verify the purchase token sent from the client using the Google Play Developer API.

What You Need

  • A service account from Google Cloud Console (JSON key file)
  • Grant that service account access in Google Play Console

Steps

1. Create a Service Account in Google Cloud Console

  1. Open Google Cloud Console
  2. Create a project (or select an existing one)
  3. Under APIs & Services → Library, search for “Google Play Android Developer API” and enable it
  4. Go to IAM & Admin → Service Accounts → Create
    • Name it something like play-verification
    • No role needed (you’ll grant permissions from the Play Console side)
  5. On the created account’s Keys tab → Add Key → JSON
  6. Save the downloaded .json file

Important: Enabling the API is mandatory. A JSON key created before enabling it won’t work.

2. Invite the Service Account in Play Console

There used to be a dedicated “API access” menu in Play Console, but it has been removed. You now add service accounts through “Users and permissions” instead.

  1. Open Google Play Console
  2. Left menu → Users and permissions
  3. Invite new users
  4. Enter the service account’s email address in the email field
    • Format: xxxx@project-id.iam.gserviceaccount.com
    • Found in the client_email field of your JSON key file
  5. Grant both permissions:
    • View financial data ← includes access to the Purchases API
    • Manage orders and subscriptions ← also required for verification

3. Using the API Server-Side (PHP Example)

// Using the google/apiclient library
$client = new Google_Client();
$client->setAuthConfig('/path/to/service-account.json');
$client->addScope('https://www.googleapis.com/auth/androidpublisher');

$service = new Google_Service_AndroidPublisher($client);
$purchase = $service->purchases_products->get(
    'com.your.package',  // package name
    'product_id',        // product ID
    'purchaseToken'      // token sent from Flutter
);

// $purchase->purchaseState === 0 means purchased

Common Pitfalls

Can’t Find the “API access” Menu

The Play Console UI changed — “API access” has been removed. It’s not under “Linked services” either. Service accounts are now invited through “Users and permissions.”

Created the JSON Key Before Enabling the API

Enable “Google Play Android Developer API” in the same project, and your existing JSON key will work as-is. No need to recreate it.

Unclear Which Permissions to Grant

You need both:

  • View financial data ← includes Purchases API access
  • Manage orders and subscriptions ← also required for verification

Permissions Take Time to Propagate

This is the trickiest one. After changing permissions in Play Console or enabling an API, it can take up to 24 hours before API calls actually start working.

If you’re getting 401/403 errors right after setup and the config looks correct, it’s likely a “waiting for Google’s cache to update” situation. Letting it sit for a few hours or overnight and trying again often resolves it.

How to interpret error codes:

  • 400 (Bad Request): Definitely a configuration error. The request format or parameters are wrong. Waiting won’t fix it.
  • 401 (Unauthorized): Tricky one. Could be a config error, but it can also appear even with correct settings while Google Cloud propagates the new service account. Hard to distinguish.
  • 403 (Forbidden): Authentication passed but no permission. If the config is correct, this is a “wait for propagation” situation. Waiting is the right call.
  • 404 (Not Found): Package name typo. Common when you use different package names for debug and release builds.

The 401 trap: Conventionally “401 means misconfiguration,” but during my testing I confirmed a freshly created service account can return 401 even when everything is configured correctly. The theory is that Google Cloud’s auth servers haven’t yet propagated the new service account and API enablement before the 403-prone Play Console permissions kick in. If you’ve reviewed the config multiple times and can’t find any mistakes, try waiting several hours to overnight — it often clears up on its own.

Additional Setup Required for Testing

Separate from the service account, you need to register the Google account on your test device in “License testing.”

Location: Play Console → Setup → License testing

Without this, the test card options (Succeed/Fail) won’t appear and you’ll be presented with a real credit card payment screen.

Also, billing testing doesn’t work when the app is in “Draft” state. You need to upload an APK/AAB to the Internal Testing or Closed Testing track and have the status show “Active.”