Between the call and the verdict.
This page follows one SCG.Validate() call from the headset to SCG’s server and back: the challenge, the attestation inside the secure hardware, the sealed payload, the eight gates, the signed verdict, and what your game does with it. Where a step rests on hardware proof and where it rests on a report, it says which.
- One call
SCG.Validate()in your start logic — no background loop, re-validation only when you ask- Proven
- The attestation chain, verified boot, bootloader lock, package name and signing certificate — signed by the headset’s secure hardware
- Reported
- Root, hook frameworks, debugger, ADB, developer mode, installer — collected by the SDK, sealed in transit, weighed by your rules
Headset
- 1Challenge — the server issues a single-use nonce
- 2Attest — the secure hardware signs it into a certificate chain
- 3Seal and send — the payload travels sealed to the server
- 4Eight gates — in order; any failure stops the pipeline
- 5Verdict — a signed token, or an error and nothing
- 6Your move — forward the token; decide what a failure means
1.0 The server issues a challenge
SCG.Validate() loads your API key and asks SCG’s server for a nonce before it touches any hardware. The nonce is random, issued to your game, single-use, and expires 30 seconds after it is issued.
Everything that follows is bound to it. The headset signs it into the attestation certificate, the payload carries it both inside and outside the seal, and gate 4 consumes it exactly once. A captured session is worthless against a different nonce, and a nonce is worthless twice.
The API key travels in a request header, so a bad key is refused here — before any cryptography runs and before a nonce is spent.
single use · expires in 30 seconds · issued to your game
- Issued to
- Your gameKeyed to the game the API key belongs to
- Lifetime
- 30 secondsServer-side expiry, independent of the timestamp window
- Uses
- OneConsumed atomically at gate 4; two copies of one request cannot both pass
- Bound into
- The attestation, the sealed payload, and the readable envelope
2.0 The headset signs the nonce inside its secure hardware
The bridge asks the Android KeyStore for a new key pair with this session’s nonce as the attestation challenge. The headset’s secure hardware — the TEE — answers with a certificate chain, and the bridge deletes the key as soon as the chain has been read.
The leaf certificate is signed by the hardware and chains to the manufacturer’s root. Inside it: the nonce, your package name, your signing certificate’s fingerprint, the verified-boot state, whether the bootloader is locked, and the security level of the key. None of that can be written by the app. Decompiling the SDK exposes its logic, but the chain is produced by hardware the app cannot reach.
At the same time the SDK collects what only software can see — device model, installer, ADB, developer mode, an attached debugger, root paths — computes the hashed device ID and runs its own scan. These are reports, not proofs. They are sealed in transit so nobody can rewrite them on the wire, but an app that has been hooked could still misreport its own state at the source. That is why they are yours to weigh in gate 8, and why hook detection and the bridge hash exist.
Proven
- nonce
- package name
- cert fingerprint
- boot: verified
- bootloader: locked
- level: TEE
Reported
- device model
- installer
- ADB
- developer mode
- debugger
- root paths
- hashed device ID
- scan result
- LProven. Written by the hardware, signed down to the manufacturer’s root, verified by the server at gate 6. Each certificate is signed by the one below it.
- RReported. Collected by the SDK, sealed for transit, weighed by the rules you set for gate 8. Evidence, not proof.
3.0 Everything the server decides on travels sealed
The native library builds the payload and seals it to SCG’s public key — X25519 key agreement with a fresh ephemeral key for every message, then AES-GCM. Anyone can seal; only the server can open.
Outside the seal, readable, are only the routing fields: sealed, nonce, timestamp, sdk_version and bridge_hash. Inside are the hashed device ID, the attestation chain, every check result and every device field. An interceptor sees an API key and an opaque blob.
The nonce and timestamp are carried both inside and outside on purpose. Someone who rewrites the readable copy cannot rewrite the sealed one without the server’s private key, so the two stop agreeing — and the server treats a mismatch as tampering, not as a flag. There is no innocent way to produce one.
What sealing buys: the payload cannot be read, edited or replayed in transit, and the attestation cannot be lifted out of it. What it does not buy: it does not turn a reported field into a proven one. Then the bridge posts it to /v1/validate.
Body · readable
noncetimestampsdk_versionbridge_hash
sealed · X25519 + AES-GCM
fresh key per message · only the server can open it
noncetimestamphwiddevice fieldsattestation chaincheck results
Violet = a second copy that must agree with the readable one above. That is the tripwire.
- 1Header. The API key. A bad key is refused before any cryptography runs.
- 2Readable. Routing fields only — worth nothing to an interceptor.
- 3Sealed. Everything the gates decide on. The duplicated nonce and timestamp are the tripwire: edit the readable copy and the sealed copy gives you away.
4.0 Eight gates, in order
The server opens the seal and runs the gates in order. Any failure stops the pipeline — the game gets an error code or FAILED, never a token. Between gate one and gate two it also compares the readable and sealed copies of the nonce and timestamp, applies its rate limits, checks bans — yours, the network blacklist and the cross-game threshold you configure — and confirms the account is active and inside its quota.
Three kinds of check. The tick beside each gate says where its evidence comes from.
- Hardware-proven — gate 6 rests on a certificate chain signed inside the headset’s secure hardware.
- Server-checked — gates 1–5 and 7 compare the request against the server’s own records: versions, clocks, nonces, baselines.
- Client-reported — gate 8 weighs signals the SDK collected on the device and sealed for transit. Evidence you weight, not proof.
- 01Required fields server-checked
sealed,bridge_hash,nonceandtimestampare all present in the body. Missing or malformed data fails here; nothing is partially accepted.SCG_INVALID_PAYLOAD - —Then: unseal and pre-checksThe seal is opened; a blob that will not open is refused. The readable nonce and timestamp must equal the sealed copies — a mismatch is logged as tampering and fails outright. Then the rate limits, the three ban checks (your ban list, the network blacklist, the cross-game threshold you set), the account’s active flag and its monthly quota.
SCG_INVALID_PAYLOAD/RATE_LIMITED/FAILED/CHECK_DASHBOARD/QUOTA_EXCEEDED - 02SDK version and bridge server-checkedThe SDK version is known and at or above the minimum you allow; the bridge binary’s hash matches the one we shipped for that version. A modified bridge is turned away before any expensive verification runs.
SDK_OUTDATED/SDK_FAILED - 03Timestamp server-checkedThe payload was built within 30 seconds of server time; a device clock up to five seconds ahead is tolerated. Captured requests go stale fast.
TIMESTAMP_INVALID - 04Single-use nonce server-checkedIssued to this game, unexpired (30-second lifetime) and never seen before. It is consumed in one atomic delete, so two copies of one request cannot both pass — that race is closed by construction.
NONCE_INVALID - 05SDK not retired server-checkedVersions we have retired are refused outright, whatever the minimum says.
SDK_OUTDATED - 06Hardware attestation hardware-provenThe part a decompiler cannot forge. The chain parses and each certificate is signed by the next. It ends at the manufacturer’s pinned root — the hard boundary, since a self-signed chain would satisfy everything else. The leaf carries the key-attestation extension at TEE level; its embedded challenge equals this session’s nonce; the attested package name and certificate fingerprint are yours; boot is verified and the bootloader is locked.
TEE_FAILED+ reason - 07Baseline fingerprint server-checkedPackage name and certificate fingerprint match the baseline the server learned over your first validations and then locked. Gate 6 already proved the attested pair; this one catches a game whose registration and build have drifted apart.
BASELINE_MISMATCH - 08Your configured checks client-reportedRoot, hook frameworks, debugger, ADB, developer mode and installer source, as reported by the SDK; plus verified-boot and bootloader-lock, which come from the attestation. In the dashboard each one is an instant fail, a flag that counts toward a limit, or ignored — your rules. Six of the eight signals are reports, not proofs.
FAILED - —After: reputation and ceilingThis device’s network fail-rate against the threshold you set; then your plan’s per-minute ceiling; then the token is signed and the verdict logged to your dashboard.
FAILED/RATE_LIMITED
Gates 1–7 answer with an error and no verdict. From the ban checks onward, anything that is a judgement about a real, identified device — a ban, gate 8, the network fail-rate — comes back as a 200 with result: FAILED. A 200 is not a pass; read the result.
5.0 A signed verdict, or an error and nothing
Two shapes come back. A gate that rejects the request answers with an error code and no verdict — TEE_FAILED arrives with a reason, BASELINE_MISMATCH with the field that differed. A decision about a real, identified device — a ban, gate 8, the network fail-rate, or a pass — answers 200 with result in the body.
PASSED arrives with a signed token: a JWT your own server can verify, valid for five minutes, or sixty seconds when Photon custom auth is on. FAILED arrives with nothing. There is no token for a failed session to be tricked with, and the reason stays in your dashboard, not on the client.
The SDK caches the verdict for the session. Validate() called again returns the cached value; ReValidate() runs the full pipeline again, and the bridge allows that at most once every two minutes per player.
4xx · errorno verdict · no token
{ "error": "SCGResult.TEE_FAILED",
"reason": "BOOTLOADER_UNLOCKED" }
200 · verdictread result
{ "result": "PASSED",
"token": "eyJhbGciOi…" }
{ "result": "FAILED" }
no token · reason in your dashboard
- AError. A gate refused the request. The code names the gate; nothing about the device has been judged.
- BVerdict. The device was judged.
PASSEDcarries the token;FAILEDcarries nothing your game can be tricked with.
6.0 SCG reports. You decide.
Branch on the result. PASSED: load the game and forward the token — to your own server, or to Photon as a custom-auth parameter before ConnectUsingSettings(), where it is verified server-side on every room join, so a bypassed client still cannot walk into a lobby. FAILED: kick, ban or flag for review; SCG never bans on its own. NO_CONNECTION: not a security failure — allow limited offline play if that suits your game and validate again when the connection returns.
In the dashboard you choose which of gate 8’s signals are instant fails, which count toward a flag limit and which are ignored; you can whitelist device IDs; and with cross-game banning on, a device you ban is flagged across every SCG-protected game.
Re-validation is not automatic. When a fresh verdict matters — after a suspicious event, before a ranked match — call ReValidate().
using SigChainGuard;using UnityEngine; public class GameManager : MonoBehaviour{ async void Start() { SCGResult result = await SCG.Validate(); if (result == SCGResult.PASSED) { LoadGame(); // verified; forward the token } else if (result == SCGResult.FAILED) { // your call: kick, ban, or flag } else if (result == SCGResult.NO_CONNECTION) { // offline; not a failure } }}
What each result means
Seventeen values. Eight come from the server; nine come from the SDK itself and never cross the wire. Branch on PASSED, FAILED and NO_CONNECTION first — the rest tell you why a session never reached a verdict. Gate errors such as TEE_FAILED or NONCE_INVALID arrive on the error field; the ledger in 4.0 names each one.
| SCGResult | Kind | From | Meaning |
|---|---|---|---|
| PASSED | pass | server | Every gate passed. A signed token comes with it. |
| FAILED | fail | server | A verdict about an identified device: banned in your game or on the network, a gate 8 instant-fail or flag limit, or the network fail-rate. The reason is in your dashboard, not on the client. |
| NO_CONNECTION | warn | SDK | No route to SCG’s server. Not a security failure; your game decides. |
| NO_CONNECTION_NOPS | warn | SDK | ValidatePS() could not reach the Photon endpoint configured in your dashboard. |
| PHOTON_ONLINE | pass | SDK | ValidatePS() reached your configured Photon endpoint. |
| SDK_OUTDATED | fail | server | The SDK version is unknown, below the minimum, or retired. Update from the dashboard. |
| BASELINE_MISMATCH | fail | server | Package name or certificate fingerprint differs from the baseline the server learned for your game — a re-signed or modified build. If you changed signing keys on purpose, re-register. |
| RATE_LIMITED | warn | server | ReValidate() inside its two-minute window, your plan’s per-minute ceiling, or a device the server has throttled. Carries retry_after. |
| QUOTA_EXCEEDED | warn | server | Monthly active players are past the plan’s grace allowance. |
| AUTH_PAUSED | warn | server | You paused validation from the dashboard toggle. |
| CHECK_DASHBOARD | fail | server | The account is not active: subscription lapsed, key not yet verified, or registration incomplete. |
| BACKEND_TIMEOUT | warn | SDK | The server did not answer inside the SDK’s timeout. Retry. |
| BRIDGE_TAMPERED | fail | SDK | The SDK’s own integrity check found the bridge is not the binary we shipped. |
| HOOK_DETECTED | fail | SDK | The SDK found a hook framework or an injected library at runtime. |
| REPLAY_DETECTED | fail | SDK | A payload was submitted with a nonce that had already been used. |
| COLLECTION_ERROR | warn | SDK | The bridge could not collect a required device field. Should not happen on a supported headset (Android API 29 or newer). |
| UNKNOWN | warn | SDK | The response could not be mapped to a result. Should not happen; update the SDK and contact support if it persists. |
What SCG deliberately does not do
A security tool that oversells itself is one you should not trust. These are the edges of the mechanism above, stated plainly.
- Re-validate on its own.
- Passing at launch is a verdict about launch. Nothing re-checks in the background; call
ReValidate()when a fresh verdict matters, at most every two minutes per player. - Ban on its own.
- SCG reports. The kick, ban or flag is your code’s decision, and the reason stays in your dashboard.
- Prove software signals.
- Root, hook, debugger, ADB and developer-mode flags are reported by the SDK’s own checks and sealed in transit. Only the attestation — chain, boot state, bootloader lock, package, certificate — is hardware-proven. Weigh gate 8 as evidence.
- Treat device identity as permanent.
- The device ID is a hash of hardware-backed values a normal app can read. A factory reset or certain OS updates can change it, so returning devices are re-detected rather than assumed.
- Treat offline as a failure.
NO_CONNECTIONis its own result. Your game decides what limited play looks like; validate again on reconnect.- Collect personal data.
- Hashed device identity, certificate data, attestation results and scan results. No names, accounts, emails or locations. Details in the privacy policy.
- Claim a headset before testing it.
- Meta Quest 2, Quest 3 and Quest Pro today. Pico and other Android VR headsets are on the roadmap, not the supported list — a headset is listed only once it has passed hardware testing.
- Publish figures it has not measured.
- No verdict time and no build-size figure until they have been measured on real headsets. Nothing on this page is a benchmark.
Questions about the mechanism
Straight answers. Where a figure has not been measured yet, the answer says so instead of guessing.
The full FAQ, including pricingDoes it work without the Meta Store or AppLab?
Yes. Distribution does not matter — Meta Store, AppLab, sideloaded or a dev build — on Meta Quest and Android VR headsets with a hardware TEE. There is no dependency on Google Play Services or Meta’s platform SDK.
Can a modder bypass SCG by decompiling the APK?
Decompiling exposes the SDK’s logic, but the security does not depend on that logic staying secret. Validation is gated on an attestation certificate signed inside the headset’s secure hardware, and the server verifies that it chains to the manufacturer’s hardware root. Full APK access still does not let an attacker produce a passing certificate without compromising that hardware — which, on a locked, non-rooted headset, we have no known method for. Root or an unlocked bootloader changes the calculus, which is exactly why device-integrity and root signals are surfaced to you.
Does SCG collect personal player data?
No. SCG stores hashed device identifiers, validation results and device signals — never names, accounts, emails or locations. Device data is hashed before it leaves the headset and is not stored in plaintext. Full details are in the privacy policy.
What if a player has no internet?
Validate() returns NO_CONNECTION, which is not a failure. Your game decides what an offline session may do; we recommend allowing limited offline play and validating again when the connection returns.
How does cross-game banning work — and what are its limits?
Ban a device in your game with cross-game banning on and its hashed ID is flagged for every SCG-protected game; each game’s own network-ban threshold decides when that flag becomes a block, with no action needed from the other developers. The honest limit: the ID is built from hardware-backed values a normal app can read, which can change after a factory reset or certain OS updates, so device identity is treated as strong but not absolute. We will not pretend a non-privileged app can read an unchangeable hardware serial.
Which headsets are supported?
Meta Quest 2, Quest 3 and Quest Pro. Pico and other Android VR headsets with a hardware TEE are on the roadmap — we plan to expand to them where the hardware allows — but they are not supported today: our focus is getting Meta Quest right first. A headset is listed as supported only once it has passed hardware testing. PC VR is planned for a later release.
Is server-side verification available on every plan?
Yes. JWT verification for your own game server is on every plan including Starter — you should never need a higher tier just to check a verdict server-side.
Read the pipeline before you trust it.
Every gate, every result and every limit on this page is in the documentation in more detail, with the SDK reference beside it. Start there; the beta is invite-only while the SDK is hardened.