esc

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 required

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.

  1. 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.
  2. 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.
  3. 03Download the Unity packageFrom Downloads, once you are signed in. One .unitypackage carries the C# API, the Java bridge and the native library.
  4. 04Import into UnityAssets → Import Package → Custom Package, select the downloaded file, then Import All.
  5. 05Configure the SDKWindow → SigChain Guard → Setup. Paste the API key from the dashboard; it is stored in Resources/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.
  6. 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.

Using Photon?

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.

GameManager.csC#
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()

Task<SCGResult>async

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.
ValidateC#
SCGResult result = await SCG.Validate();if (result == SCGResult.PASSED) { // safe to proceed }

SCG.ReValidate()

Task<SCGResult>async

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
Rate limits

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.

ReValidateC#
SCGResult check = await SCG.ReValidate();if (check == SCGResult.FAILED) { // handle fail }

SCG.ValidatePS()

Task<SCGResult>async

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_ONLINE or NO_CONNECTION_NOPS
needs
A Photon endpoint set in your dashboard
ValidatePSC#
SCGResult ps = await SCG.ValidatePS();if (ps == SCGResult.PHOTON_ONLINE) { // matchmaking logic }

SCG.HWIDPlayer()

Task<string>async

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()
HWIDPlayerC#
string hwid = await SCG.HWIDPlayer();if (!string.IsNullOrEmpty(hwid)) { Debug.Log("HWID: " + hwid); }

SCG.ReturnHwid()

stringsync

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
ReturnHwidC#
string hwid = SCG.ReturnHwid(); // no network call
Not listed above · 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.

Two response shapes

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.

SCGResultKindMeaning
PASSEDserverpassAll eight gates passed: verified boot, locked bootloader, matching certificate and package, and your configured checks clear. A signed token is issued.
FAILEDserverfailA 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_CONNECTIONdevicewarnNo route to SCG’s server. Not a security failure — your game decides what an offline session may do; validate again on reconnect.
NO_CONNECTION_NOPSdevicewarnFrom ValidatePS(): the Photon endpoint is unreachable or not configured in your dashboard. Check the endpoint URL there.
SDK_OUTDATEDserverfailThe 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_TAMPEREDdevicefailThe SDK bridge on the device differs from its compiled state. A serious integrity signal.
HOOK_DETECTEDdevicefailA known hook framework or an injected library was found on the device at runtime.
RATE_LIMITEDserverwarnReValidate() 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_EXCEEDEDserverwarnMonthly 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_PAUSEDserverwarnYou paused validation from the dashboard toggle. Every call returns this until you resume.
BACKEND_TIMEOUTdevicewarnThe server did not answer inside the SDK’s timeout window. Retry; contact support if it persists.
CHECK_DASHBOARDserverfailThe 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_ERRORdevicewarnThe bridge could not collect one or more required device fields. Should not happen on a supported headset — check the API 29 minimum.
REPLAY_DETECTEDdevicefailThe SDK caught a payload being submitted a second time. Server-side, a reused nonce is refused at gate 4 as NONCE_INVALID.
BASELINE_MISMATCHserverfailThe 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_ONLINEdevicepassFrom ValidatePS(): the configured Photon endpoint is reachable. Safe to start matchmaking.
UNKNOWNdevicewarnThe server answered with something the SDK has no case for. Should not occur; update the SDK and tell support if it persists.
Dashboard-only reasons

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.

ErrorRaised byHTTPMeaning
SCG_INVALID_PAYLOADGate 1400A 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_HWIDPre-gate400The HWID is not a 64-character hex hash.
SCGResult.SDK_OUTDATEDGates 2, 5400Unknown version, or below the minimum allowed; gate 5 refuses retired versions.
SCGResult.SDK_FAILEDGate 2400The 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_INVALIDGate 3400The 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_INVALIDGate 4400The 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_FAILEDGate 6400Hardware 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_MISMATCHGate 7400Carries field, naming which baseline differed: package name or certificate fingerprint.
SCGResult.CHECK_DASHBOARDPre-gate403The account is not active.
SCGResult.QUOTA_EXCEEDEDPre-gate429Monthly active players past the grace allowance.
SCGResult.AUTH_PAUSEDValidation paused from the dashboard.
RATE_LIMITEDLimiter429Carries 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.

API key errors

Answered before any gate runs, by the API key check.

ErrorKindMeaning
SCG_NO_API_KEYfailThe API key header is absent.
SCG_INVALID_API_KEYfailThe key matches neither the current key nor the previous one.
SCG_API_KEY_RETIREDfailA previous key, past its seven-day overlap after a rotation. A build still carrying it needs the new key.
SCG_GAME_NOT_FOUNDfailThe key is valid but no game row exists for it.
SCG_SANDBOX_MODEwarnSandbox restriction.
ROTATION_IN_PROGRESSwarnA key rotation is underway. Retry shortly.
SCG_INTERNAL_ERRORwarnSomething failed server-side; a generic 500, with the stack logged on the server. Retry, then contact support.
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.

  1. 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 SCGResult set — 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
  2. 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.