- Android — Google Play Integrity API
- iOS — Apple App Attest
The Synheart
app_id (shown on the App in the platform dashboard) is the platform’s identifier for that App record — a short code like app_focus_and_kB8mPx. Your Android packageName and iOS bundle ID are separate values you also configure on the App. Don’t conflate them: Play Console steps below refer to your Play listing’s packageName, not the platform app_id.How Android attestation works at Synheart
Google Play Integrity’sdecodeIntegrityToken is authorized per Google Cloud project: only callers that can act as your Cloud project may decode tokens minted for your packageName. You supply that authorization by creating a dedicated service account in your own Cloud project and uploading its JSON key to your App’s settings page. Synheart stores the key encrypted at rest and uses it server-side only to call decodeIntegrityToken for your packageName.
This means:
- The credential authorizes one specific operation against one specific package.
- Decode calls bill against your Cloud project’s quota.
- If you rotate or revoke the key in Cloud Console, decode access stops immediately — no Synheart-side action needed.
- iOS attestation needs no equivalent credential: App Attest verifies against Apple’s global public CA chain.
Configure attestation in the dashboard
For each App in the platform dashboard, open App settings → SDK attestation and fill in the block. Fields differ by OS:| OS | Required | Optional |
|---|---|---|
| Android | Bundle ID (your Android packageName), Google project ID (the GCP project linked to Play Console), Play Integrity decoder credential (uploaded SA JSON) | Allow development builds — accept attestations from flutter run / debug-signed / emulator builds |
| iOS | Bundle ID (your iOS bundle ID), Apple Developer Team ID | Allow development builds — accept attestations from Xcode-installed builds and TestFlight internal testing |
Android — Play Integrity setup
You complete a few one-time steps in Play Console and your own Google Cloud project, then upload one credential to the platform dashboard.Before you start
You need:- A Google Play Developer account and access to Play Console for the app you want attested.
- The app created in Play Console (a draft or any release track is enough).
- Owner or IAM Admin access on a Google Cloud project you’ll dedicate to this app.
Steps
Link Play Console to your Cloud project
Play Integrity decode authorization is granted by the Cloud project linked to your Play Console app, not by an IAM role you grant Synheart.
- Open Google Play Console and select your app.
- In the per-app left nav, open Protected with Play → Play Integrity API settings.
- Confirm the Google Cloud project row shows your own GCP project. If it doesn’t, click Edit project and pick your project from the picker.
Enable the Play Integrity API in that Cloud project
- Cloud Console UI
- gcloud CLI
Switch the project picker in Google Cloud Console to the project you linked above, then:Open the Play Integrity API library page →Click Enable. If the button already says Manage, the API is on — you can continue.
Create a service account scoped to Play Integrity
- Cloud Console UI
- gcloud CLI
Still in Google Cloud Console, in the same project:
- Open IAM & Admin → Service Accounts.
- Click + Create Service Account.
- Name it predictably, e.g.
play-integrity-decoder. - On Grant this service account access to project, give it one role: Service Usage Consumer (
roles/serviceusage.serviceUsageConsumer). - Skip Grant users access to this service account and click Done.
- Click into the service account.
- Open the Keys tab.
- Add key → Create new key → JSON.
- Save the downloaded
.jsonsomewhere temporary.
.json like a password — anyone with this file can decode integrity tokens for your app.Play Integrity decode authorization itself comes from the Play Console linkage in step 1, not from a project IAM role — the SA only needs to be able to call an enabled API in your Cloud project, which is exactly what Service Usage Consumer grants. Don’t add Owner, Editor, or other broad roles.
Set the App's attestation fields in the platform dashboard
On the matching App in the platform dashboard, under SDK attestation → Android:
bundle_id— your AndroidpackageName(for examplecom.example.focus).google_project_id— the GCP project ID from step 1. It must match the project_id inside the SA JSON you’ll upload next; if they disagree the upload is rejected.- Allow development builds — keep off for Play Store releases. Turn it on only while testing emulator or debug-signed builds (
flutter run,./gradlew installDebug); the server then acceptsUNRECOGNIZED_VERSIONand emulator verdicts that would otherwise fail.
Upload the service account JSON
Click Edit again, then under Play Integrity decoder credential click Upload SA JSON… and pick the file from step 3.The dashboard validates the file (shape, You can delete the downloaded JSON from your machine now — re-creating it from Cloud Console takes one click if you ever need to.
type == "service_account", project_id match) and stores it encrypted. On success the card flips to:Rotating the credential
If the JSON key leaks or you rotate on a schedule:- Create a fresh JSON key in Cloud Console (same SA, or a new one in the same project).
- In the dashboard, click Replace… on the credential card and upload the new file.
- Delete the old key in Cloud Console.
Removing the credential
Click Remove. There is no fallback decoder — every device registration for your app will start failing withDEV_003 immediately and continue failing until a new credential is uploaded. Only remove it if you mean to.
What gets enforced
Synheart callsdecodeIntegrityToken using your service account and checks:
- Real device. Tokens from emulators or virtual devices are rejected unless Allow development builds is on.
- Recognized app. The build must match a Play-distributed version of your
packageName. Sideloaded or repackaged builds fail. - Token freshness. Tokens older than five minutes are rejected to prevent replay.
roles/serviceusage.serviceUsageConsumer) lets the SA call enabled APIs in your project. It cannot read your store listing, publish releases, view financials, or access end-user data.
If your org disables service account key creation
Some Google Cloud orgs enforceconstraints/iam.disableServiceAccountKeyCreation, which blocks step 3’s JSON-key download. You’ll see this error from gcloud:
Option A — Self-service workaround (if you have orgpolicy.policy.set)
If your role on the Cloud project lets you set org policies, you can temporarily override the constraint at the project level, mint the one key you need, then restore the enforcement. The key keeps working; only future key creation is blocked again.
play-integrity-decoder.json via the dashboard as in step 5. Your project is now back to the same security posture as before, with one extra credential outstanding which you can revoke via Cloud Console any time you rotate.
Option B — Workload Identity Federation (no key download required)
Workload Identity Federation lets Synheart mint short-lived federated tokens to act as your service account, with no long-lived key ever leaving your org. You register Synheart’s OIDC issuer as a trust source in your project and add an IAM binding letting the federated identity impersonateplay-integrity-decoder.
WIF support is on the roadmap but not yet generally available. If you’re blocked by disableServiceAccountKeyCreation and Option A isn’t appropriate for your org, contact support@synheart.ai for early access.
iOS — App Attest setup
iOS attestation uses Apple App Attest, which is built into iOS 14+. There is no Apple-side invite step and no credential upload: the OS attests directly to Apple, and Synheart verifies the resulting attestation object against Apple’s global public CA chain.Before you start
You need:- An Apple Developer account with the app’s bundle ID provisioned.
- iOS 14 or later on a real device — App Attest is not available in the iOS Simulator.
Steps
Note your Apple Team ID and bundle ID
Both are visible at developer.apple.com/account under Membership (Team ID) and Identifiers (bundle ID).
Set the App's attestation fields in the platform dashboard
On the matching App in the platform dashboard, under SDK attestation → iOS:
bundle_id— your iOS bundle ID (for examplecom.example.focus).team_id— your 10-character Apple Developer Team ID.- Allow development builds — keep off for App Store / TestFlight external builds (the server then expects Apple’s production App Attest environment). Turn it on while testing with Xcode-installed builds or TestFlight internal testing, which use Apple’s development App Attest environment.
Build with the App Attest entitlement
Add the
com.apple.developer.devicecheck.appattest-environment entitlement to your app’s entitlements plist. Set it to production for App Store / TestFlight external, and development for Xcode-installed / TestFlight internal — and match the dashboard’s Allow development builds toggle (on when entitlement is development, off when production).What gets enforced
The Synheart verifier checks:- Apple-signed certificate chain. The attestation must chain to Apple’s App Attest root.
- Bundle ID + Team ID. The relying party hash inside the attestation must match
team_id+bundle_idconfigured on the App. - Challenge nonce. The attestation must cover the exact challenge nonce Synheart issued (5-minute TTL).
- Development vs production env. The attestation environment carried by the App Attest object must match the Allow development builds toggle on the App (Apple’s
developmentenv when the toggle is on,productionenv when off).
Client flow
Both platforms follow the same three-step pattern; the SDKs implement this for you:POST /v1/device/challenge— request a one-time challenge (returnschallenge_id,challenge_nonce, 5-minute TTL).- The OS produces an attestation over the nonce (Play Integrity token on Android, App Attest object on iOS).
POST /v1/device/register— submit the attestation. The server verifies it and binds the device to your App.
Troubleshooting
| Symptom | Platform | What to check |
|---|---|---|
service account project_id does not match the app's google_project_id on upload | Android | The dashboard’s Google project ID field has to equal the project_id inside the SA JSON. Update the field, save, then re-upload. |
Service account JSON is not valid JSON / expected "service_account" on upload | Android | Re-download the key from Cloud Console — pick JSON as the format, not P12. |
DEV_003 / device registration failing immediately | Android | The Cloud project linked under Play Console → Protected with Play → Play Integrity API settings must equal the dashboard’s Google project ID, and the uploaded SA must live in that same project. |
| Credential upload returns “Unauthorized” | Android | Hard-refresh the dashboard page (Cmd+Shift+R) to renew the session, then retry. |
attestation environment mismatch | iOS | The entitlement (production / development) must match the dashboard production toggle on the App. |
App Attest not supported at runtime | iOS | App Attest requires iOS 14+ on a real device. Simulator builds cannot attest. |
| Tokens rejected as too old | Both | The challenge expires after five minutes. Re-fetch a fresh challenge before attesting. |
Need help?
Contact support@synheart.ai with your Synheartapp_id, the bundle_id you configured, and the time of the failing attestation. For Android, include the credential fingerprint shown on the App settings page (a short blake2s hex). For iOS, include the entitlement value from your build.
Next
API keys
Issue per-app keys and understand ingestion allowlisting.
Synheart Auth
How registered devices sign every outbound request once attestation has succeeded.