SDK reference
Everything the SCG static class exposes, every SCGResult it can return, and how to get a Unity VR game set up. One Unity package import, one call — the headset’s secure hardware attests the session, SCG’s server runs eight gates, and your game acts on the verdict.
- Engine
- Unity 6 (6000.x) · Unity 2022.3 LTS
- Target
- Android API 29+ · Meta Quest today; Pico and other Android VR headsets with a hardware TEE planned
- Status
- Private beta · accounts are invite-only while the SDK is hardened · methods and behaviour may change before public launch · talk to support before shipping it in production
System requirements
The SDK is one .unitypackage: the C# API your game calls, a Java bridge, and a native arm64-v8a library that talks to the headset’s secure hardware. Attestation needs Android 10 or newer, so anything below API 29 has no route to the TEE and is not supported.
Distribution does not matter — Meta Store, AppLab, sideloaded or a dev build — and there is no dependency on Google Play Services or Meta’s platform SDK. The SDK does need a route to SCG’s server at validation time; without one, Validate() returns NO_CONNECTION, which is not a failure.
- Engine
- Unity 6 (6000.x) · Unity 2022.3 LTSTested on Unity 6.4 and 2022.3
- Android
- API 29+ (Android 10)Required for hardware TEE attestation
- Headsets
- Meta Quest and Android VR with a hardware TEESupport is claimed for a headset only once it has passed hardware testing
- Package
- One
.unitypackageC# API · Java bridge · native arm64-v8a library - Account
- A registered game and an API keyPrivate beta: invite-only, a beta key is needed to create an account, nothing is charged
- Network
- Meta Store, AppLab or noneNo Google Play Services or Meta platform SDK dependency
Getting set up
The Unity package comes from your dashboard. Before the SDK returns real verdicts you need an account, a registered game, and a verified API key — that verification is BTKV.
BTKV — Beta Test Key Verification — is how SCG confirms you own the app you are registering. Until it is complete and the account is active, every validation call returns CHECK_DASHBOARD. Finish it in the dashboard and the same key starts working without a rebuild.
- 01Create an accountGo to sigchainguard.com/dashboard/ and sign up — during the private beta a beta key is required. Verify your email; you land in the dashboard.
- 02Register your game (BTKV)In the dashboard, open Register Game and upload your signed
.apk. SCG extracts the certificate fingerprint and package name itself. This is the BTKV step: when it completes, your API key is issued and bound to that certificate, so a re-signed or repackaged build presents a different certificate and is turned away. - 03Download the Unity packageFrom Downloads, once you are signed in. One
.unitypackagecarries the C# API, the Java bridge and the native library. - 04Import into Unity
Assets → Import Package → Custom Package, select the downloaded file, thenImport All. - 05Configure the SDK
Window → SigChain Guard → Setup. Paste the API key from the dashboard; it is stored inResources/SCGConfig.asset, which ships empty. The setup window’s Fix All checks your project settings and resolves the common integration problems, and a pre-build check runs again when you build. - 06Call
SCG.Validate()Add the call to your game’s start logic and await it before any gameplay code runs. The next section is the whole integration.
First integration
One async call in your start logic. The bridge collects the hardware attestation, seals the payload and sends it to SCG’s server; your game has the verdict before any gameplay code runs. Three branches cover every session.
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: the reason stays in your dashboard, and whether that is a kick, a ban or a quiet flag is your call. NO_CONNECTION is a distinct result, not a failure — allow limited offline play if that suits your game and validate again on reconnect.
With Photon custom auth on, pass the token as a custom-auth parameter before ConnectUsingSettings(). SCG verifies it server-side on every room join, so a bypassed client still cannot walk into a lobby — and the token’s lifetime drops to sixty seconds so a stale one cannot be replayed. The accessor the live docs name for the token is covered in the note under SDK methods.
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 } }}
SDK methods
Every public method on the SCG static class, as it ships in the package’s SCG.cs. Validate() is open below; the rest expand. Each entry carries the signature, the return type, what it does, and a C# example you can copy. SCG.cs also holds Initialize() and IsInitialized() — internal lifecycle, not something your game calls.
SCG.Validate()
The primary validation call. It fires the SCG bridge, collects the hardware attestation and device signals, seals the payload and sends it to SCG’s server, which runs the eight gates and answers with a verdict. It runs once per session — a second call returns the cached result. The player’s HWID is tracked in your dashboard on every call.
- returns
Task<SCGResult>— every value is in the SCGResult table- runs
- Once per session, then cached. For a fresh verdict mid-session call
ReValidate(). - on PASSED
- A signed JWT, valid five minutes — sixty seconds when Photon custom auth is on. Forward it to your own server or to Photon.
SCGResult result = await SCG.Validate();if (result == SCGResult.PASSED) { // safe to proceed }
SCG.ReValidate()
A manual mid-session re-check for the current player. It runs the full pipeline identically to Validate(). The SDK bridge caps it at roughly once every two minutes per player; a call inside that window returns RATE_LIMITED. Use it when your game sees something suspicious and wants a fresh verdict — before a ranked match, after an odd event. Nothing re-validates on its own.
- returns
Task<SCGResult>- cap
- About once every two minutes per player, enforced by the bridge on the device
The two-minute cap is a client-side guard, not a security boundary. The server applies its own limits: your plan’s validations-per-minute ceiling (120 on Starter, 1,200 on Growth, 12,000 on Studio, shared across all players of the game), a per-IP nonce ceiling of 30 a minute, and a penalty box for repeat offenders. Every one of them answers RATE_LIMITED, and the server’s response carries retry_after.
SCGResult check = await SCG.ReValidate();if (check == SCGResult.FAILED) { // handle fail }
SCG.ValidatePS()
Photon-specific validation: confirms that the Photon endpoint configured in your dashboard is reachable. Call it before connecting a player to a multiplayer session. It answers PHOTON_ONLINE when the endpoint responds, and NO_CONNECTION_NOPS when it is unreachable or not configured.
- returns
Task<SCGResult>—PHOTON_ONLINEorNO_CONNECTION_NOPS- needs
- A Photon endpoint set in your dashboard
SCGResult ps = await SCG.ValidatePS();if (ps == SCGResult.PHOTON_ONLINE) { // matchmaking logic }
SCG.HWIDPlayer()
Registers the current player’s HWID against your game in SCG’s records and returns the HWID string. Call it after a successful Validate() if you want to trigger registration at a specific point in your code; if the device has been seen in other SCG-protected games, its existing record is linked and no duplicate is created. HWID tracking already happens through Validate(), so most games never need this.
- returns
Task<string>— the player’s HWID- when
- After a successful
Validate()
string hwid = await SCG.HWIDPlayer();if (!string.IsNullOrEmpty(hwid)) { Debug.Log("HWID: " + hwid); }
SCG.ReturnHwid()
Returns the device’s cached HWID synchronously, with no network call. The HWID is a 64-character hex hash derived from the strongest hardware-backed signals the platform exposes to an app. It survives reinstalls, cleared data and account changes; a factory reset or certain OS updates can change it — so treat it as strong, not permanent. Use it when your own code needs the string, or to show it to a player.
- returns
string— 64 hex characters- network
- None
string hwid = SCG.ReturnHwid(); // no network call
SCG.GetLastToken()
The live docs describe SCG.GetLastToken() as returning the signed JWT from the last successful Validate(), for Photon’s AuthenticationValues. The vault’s table of SCG.cs, verified against the extracted package on 28 Jul 2026, does not include it, so it is not documented here as a confirmed method until the package confirms it. The token itself is real and server-verified: it is issued on PASSED and lives five minutes — sixty seconds when Photon custom auth is on. If you need the accessor before this note is resolved, ask support.
SCGResult codes
Every value of the SCGResult enum — seventeen, from SCGResult.cs — with what it means and where it comes from. Values tagged device are raised by the SDK on the headset and are never sent by the server.
A gate that rejects the request answers with an error string and no verdict. A decision about a real, identified device — a ban, a gate-8 rule, the network fail-rate — comes back as 200 with result: PASSED or result: FAILED. A 200 is not a pass; read the result.
Gate errors arrive on the response’s error field and the SDK handles them separately from the result path. The enum below has no case for TEE_FAILED, NONCE_INVALID, TIMESTAMP_INVALID or SDK_FAILED, which the live docs list as SCGResult values — they are documented as server error strings.
| SCGResult | Kind | Meaning |
|---|---|---|
| PASSEDserver | pass | All eight gates passed: verified boot, locked bootloader, matching certificate and package, and your configured checks clear. A signed token is issued. |
| FAILEDserver | fail | A decision against this device: a direct ban in your game, the network blacklist, the network auto-ban threshold, a gate-8 instant-fail rule, the flag limit, or the network fail-rate. The reason is in your dashboard’s validations tab, never on the client. |
| NO_CONNECTIONdevice | warn | No route to SCG’s server. Not a security failure — your game decides what an offline session may do; validate again on reconnect. |
| NO_CONNECTION_NOPSdevice | warn | From ValidatePS(): the Photon endpoint is unreachable or not configured in your dashboard. Check the endpoint URL there. |
| SDK_OUTDATEDserver | fail | The SDK version is unknown to the server, below the minimum allowed, or retired (gates 2 and 5). Every call fails until you update from the dashboard. |
| BRIDGE_TAMPEREDdevice | fail | The SDK bridge on the device differs from its compiled state. A serious integrity signal. |
| HOOK_DETECTEDdevice | fail | A known hook framework or an injected library was found on the device at runtime. |
| RATE_LIMITEDserver | warn | ReValidate() inside its two-minute window, or the server’s limiter: your plan’s validations-per-minute ceiling, the per-IP nonce ceiling, or the penalty box. Wait for retry_after. |
| QUOTA_EXCEEDEDserver | warn | Monthly active players are past the plan’s grace allowance — sustained use beyond 125% of the limit, after the three-day grace window. Up to 110% nothing changes; between 110% and 125% you get a dashboard banner and a Discord alert. Upgrade, or wait for the next period. |
| AUTH_PAUSEDserver | warn | You paused validation from the dashboard toggle. Every call returns this until you resume. |
| BACKEND_TIMEOUTdevice | warn | The server did not answer inside the SDK’s timeout window. Retry; contact support if it persists. |
| CHECK_DASHBOARDserver | fail | The account’s active flag is off: subscription lapsed, API key inactive, or BTKV not completed. Fix it in the dashboard; the same key starts working without a rebuild. |
| COLLECTION_ERRORdevice | warn | The bridge could not collect one or more required device fields. Should not happen on a supported headset — check the API 29 minimum. |
| REPLAY_DETECTEDdevice | fail | The SDK caught a payload being submitted a second time. Server-side, a reused nonce is refused at gate 4 as NONCE_INVALID. |
| BASELINE_MISMATCHserver | fail | The certificate fingerprint or package name differs from the baseline learned at registration (gate 7). If you changed your signing key legitimately, re-register in the dashboard. |
| PHOTON_ONLINEdevice | pass | From ValidatePS(): the configured Photon endpoint is reachable. Safe to start matchmaking. |
| UNKNOWNdevice | warn | The server answered with something the SDK has no case for. Should not occur; update the SDK and tell support if it persists. |
| No SCGResult value matches. | ||
NETWORK_BLACKLIST and NETWORK_AUTO_BAN are not SCGResult values. The device gets FAILED; the reason appears in your dashboard as checks_failed. The auto-ban threshold and its message are configured in your game’s SCG-AUTH settings.
Server error strings
What the server puts on the error field when a gate or a pre-gate check rejects the request. These carry an HTTP status and no verdict. The lookup box above filters these rows too.
| Error | Raised by | HTTP | Meaning |
|---|---|---|---|
| SCG_INVALID_PAYLOAD | Gate 1 | 400 | A required field is missing — sealed, bridge_hash, nonce or timestamp — or the sealed blob would not open, or the readable nonce and timestamp disagree with the sealed copies. The reason is logged, not echoed. |
| SCG_INVALID_HWID | Pre-gate | 400 | The HWID is not a 64-character hex hash. |
| SCGResult.SDK_OUTDATED | Gates 2, 5 | 400 | Unknown version, or below the minimum allowed; gate 5 refuses retired versions. |
| SCGResult.SDK_FAILED | Gate 2 | 400 | The bridge hash does not match the one registered for that SDK version — the binary is not the one shipped. Reimport a clean copy of the package; if it persists on an unmodified build, contact support. |
| SCGResult.TIMESTAMP_INVALID | Gate 3 | 400 | The payload timestamp is outside the window: up to 30 seconds stale, up to 5 seconds ahead of server time. Keep the device clock automatic; do not build payloads ahead of time. |
| SCGResult.NONCE_INVALID | Gate 4 | 400 | The nonce is unknown, expired (30-second lifetime) or already consumed. Nonces are single-use and consumed atomically, so two copies of one request cannot both pass. |
| SCGResult.TEE_FAILED | Gate 6 | 400 | Hardware attestation failed. Carries a reason — the values are listed below. Expected on emulators, rooted devices and unlocked bootloaders; if a retail headset reports it, send the reason to support. |
| SCGResult.BASELINE_MISMATCH | Gate 7 | 400 | Carries field, naming which baseline differed: package name or certificate fingerprint. |
| SCGResult.CHECK_DASHBOARD | Pre-gate | 403 | The account is not active. |
| SCGResult.QUOTA_EXCEEDED | Pre-gate | 429 | Monthly active players past the grace allowance. |
| SCGResult.AUTH_PAUSED | — | — | Validation paused from the dashboard. |
| RATE_LIMITED | Limiter | 429 | Carries retry_after. Raised by the rate buckets, the penalty box, and the per-IP nonce ceiling of 30 a minute — which also records a penalty. |
| No server error matches. | |||
API key errors
Answered before any gate runs, by the API key check.
| Error | Kind | Meaning |
|---|---|---|
| SCG_NO_API_KEY | fail | The API key header is absent. |
| SCG_INVALID_API_KEY | fail | The key matches neither the current key nor the previous one. |
| SCG_API_KEY_RETIRED | fail | A previous key, past its seven-day overlap after a rotation. A build still carrying it needs the new key. |
| SCG_GAME_NOT_FOUND | fail | The key is valid but no game row exists for it. |
| SCG_SANDBOX_MODE | warn | Sandbox restriction. |
| ROTATION_IN_PROGRESS | warn | A key rotation is underway. Retry shortly. |
| SCG_INTERNAL_ERROR | warn | Something failed server-side; a generic 500, with the stack logged on the server. Retry, then contact support. |
| No API key error matches. | ||
The reason values TEE_FAILED can carry
- PACKAGE_MISMATCH
- FINGERPRINT_MISMATCH
- UNTRUSTED_ROOT
- BOOT_NOT_VERIFIED
- CHAIN_UNREADABLE
- CHAIN_INVALID
- CERT_EXPIRED
- NO_ATTESTATION
- BOOTLOADER_UNLOCKED
- NONCE_MISMATCH
- ATTESTATION_EXTENSION_NOT_FOUND
- INVALID_CERT_CHAIN
- CERT_PARSE_FAILED
- PACKAGE_UNPARSEABLE
- FINGERPRINT_UNPARSEABLE
- SERVER_PACKAGE_UNSET
- SERVER_FINGERPRINT_UNSET
- CHAIN_SIGNATURE_INVALID…
- NOT_TEE_LEVEL…
- KEYMASTER_NOT_TEE…
- KEYDESC_PARSE_FAILED…
The last four are prefixes: the server matches the start of a dynamic message. CHAIN_INVALID, CERT_EXPIRED and NO_ATTESTATION are in the allow-list but are never raised by the attestation check today.
Release history
No version number is stated here: the site, the served package and the extracted package do not yet agree on one. The version your build reports is read from the native library at runtime and cannot be changed by patching C# or Java — so it is the only thing that decides which version the server sees.
-
Private betaCurrent · not released publicly
The initial beta. Accounts are invite-only while the SDK is hardened with beta partners, and nothing is charged. What it ships:
SCG.Validate(),SCG.ValidatePS(),SCG.ReValidate(),SCG.HWIDPlayer(),SCG.ReturnHwid()- The full
SCGResultset — seventeen values - Hardware TEE attestation at gate 6
- Sealed payloads with a single-use nonce and a 30-second timestamp window
- Bridge-hash pinning per SDK version
- Cross-game HWID ban network
- Direct game banning
- Network blacklist and auto-ban by threshold
- Custom ban messages per game
- Photon custom auth integration
- MAU tracking with the 110% / 125% overage ladder
- API key rotation with a seven-day overlap
- Unity 6 (6000.x) and Unity 2022.3 LTS
- Android API 29+
- BTKV verification flow
-
Public launchAfter the beta
Paid plans open — Starter, Growth and Studio are published early so you can plan, and prices may change before then. Support for a headset is claimed only once it has passed hardware testing. PC VR is planned for a later release. Verdict-time and build-size figures are published once they have been measured on real headsets.