Skip to main content
App attestation lets Synheart confirm that traffic comes from your real, unmodified build on a normal device, not from emulators, repackaged binaries, or scripted clients. The platform supports: Both platforms follow the same client flow: the SDK requests a one-time challenge, asks the OS for an attestation over that challenge, and registers the attested device with Synheart. You configure each App in the platform dashboard once; the SDKs handle the runtime path.
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’s decodeIntegrityToken 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:
OSRequiredOptional
AndroidBundle 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
iOSBundle ID (your iOS bundle ID), Apple Developer Team IDAllow development builds — accept attestations from Xcode-installed builds and TestFlight internal testing
Both platforms expose the same Allow development builds toggle on the App settings page. It relaxes attestation verdicts so debug / pre-release builds pass; the server then treats the app as running in dev mode. Turn it off before App Store / Play Store release.

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

1

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.
  1. Open Google Play Console and select your app.
  2. In the per-app left nav, open Protected with Play → Play Integrity API settings.
  3. 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.
Note the project ID exactly — you’ll use it in step 4.
2

Enable the Play Integrity API in that Cloud project

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.
3

Create a service account scoped to Play Integrity

Still in Google Cloud Console, in the same project:
  1. Open IAM & Admin → Service Accounts.
  2. Click + Create Service Account.
  3. Name it predictably, e.g. play-integrity-decoder.
  4. On Grant this service account access to project, give it one role: Service Usage Consumer (roles/serviceusage.serviceUsageConsumer).
  5. Skip Grant users access to this service account and click Done.
Then download a key:
  1. Click into the service account.
  2. Open the Keys tab.
  3. Add key → Create new key → JSON.
  4. Save the downloaded .json somewhere temporary.
Treat the downloaded .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.
4

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 Android packageName (for example com.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 accepts UNRECOGNIZED_VERSION and emulator verdicts that would otherwise fail.
Click Save changes.
5

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, type == "service_account", project_id match) and stores it encrypted. On success the card flips to:
Service account   play-integrity-decoder@<your-project>.iam.gserviceaccount.com
Cloud project     <your-project>
Uploaded          <timestamp> · by <you>                                  fp <short hash>
You can delete the downloaded JSON from your machine now — re-creating it from Cloud Console takes one click if you ever need to.

Rotating the credential

If the JSON key leaks or you rotate on a schedule:
  1. Create a fresh JSON key in Cloud Console (same SA, or a new one in the same project).
  2. In the dashboard, click Replace… on the credential card and upload the new file.
  3. Delete the old key in Cloud Console.
The card records the rotation time and which user did it.

Removing the credential

Click Remove. There is no fallback decoder — every device registration for your app will start failing with DEV_003 immediately and continue failing until a new credential is uploaded. Only remove it if you mean to.

What gets enforced

Synheart calls decodeIntegrityToken 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.
The service account permission above (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 enforce constraints/iam.disableServiceAccountKeyCreation, which blocks step 3’s JSON-key download. You’ll see this error from gcloud:
ERROR: (gcloud.iam.service-accounts.keys.create) FAILED_PRECONDITION:
Key creation is not allowed on this service account.
type: constraints/iam.disableServiceAccountKeyCreation
You have two options.

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.
export PROJECT_ID=your-cloud-project-id
export SA_EMAIL=play-integrity-decoder@${PROJECT_ID}.iam.gserviceaccount.com

# Enable the Org Policy API on this project (one-time)
gcloud services enable orgpolicy.googleapis.com --project="$PROJECT_ID"

# Override the constraint at project level
cat > /tmp/allow-key-creation.yaml <<EOF
name: projects/${PROJECT_ID}/policies/iam.disableServiceAccountKeyCreation
spec:
  rules:
    - enforce: false
EOF
gcloud org-policies set-policy /tmp/allow-key-creation.yaml

# Wait a few seconds for propagation, then mint the key
# (retry the create call below if the first attempt still says FAILED_PRECONDITION)
gcloud iam service-accounts keys create ./play-integrity-decoder.json \
  --iam-account="$SA_EMAIL" \
  --project="$PROJECT_ID"

# Restore the inherited enforcement
gcloud org-policies reset iam.disableServiceAccountKeyCreation --project="$PROJECT_ID"

# Wipe the local YAML
rm /tmp/allow-key-creation.yaml
Upload the resulting 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 impersonate play-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

1

Note your Apple Team ID and bundle ID

Both are visible at developer.apple.com/account under Membership (Team ID) and Identifiers (bundle ID).
2

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 example com.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.
3

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_id configured 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 development env when the toggle is on, production env when off).

Client flow

Both platforms follow the same three-step pattern; the SDKs implement this for you:
  1. POST /v1/device/challenge — request a one-time challenge (returns challenge_id, challenge_nonce, 5-minute TTL).
  2. The OS produces an attestation over the nonce (Play Integrity token on Android, App Attest object on iOS).
  3. POST /v1/device/register — submit the attestation. The server verifies it and binds the device to your App.
Subsequent SDK requests are signed by the registered device — see Synheart Auth.

Troubleshooting

SymptomPlatformWhat to check
service account project_id does not match the app's google_project_id on uploadAndroidThe 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 uploadAndroidRe-download the key from Cloud Console — pick JSON as the format, not P12.
DEV_003 / device registration failing immediatelyAndroidThe Cloud project linked under Play Console → Protected with PlayPlay 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”AndroidHard-refresh the dashboard page (Cmd+Shift+R) to renew the session, then retry.
attestation environment mismatchiOSThe entitlement (production / development) must match the dashboard production toggle on the App.
App Attest not supported at runtimeiOSApp Attest requires iOS 14+ on a real device. Simulator builds cannot attest.
Tokens rejected as too oldBothThe challenge expires after five minutes. Re-fetch a fresh challenge before attesting.

Need help?

Contact support@synheart.ai with your Synheart app_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.