In-App Verification

The modern way to verify cross-game transfers and player-to-player sends, the player approves right inside your app with a hardware-backed signature. It is fast, costs nothing to deliver, and resists SIM-swap and phishing attacks that a one-time code cannot.

Why in-app verification

SIM-swap resistant

Approval is a signature from a key sealed in the device's secure hardware, it can't be intercepted or forwarded like a one-time code.

Nothing to deliver

The approval happens on the player's own device, so there is no message to send, wait for or pay for.

Seamless UX

The player taps approve in your title, no leaving the app to find and type a code.

Additive and opt-in, nothing you have today breaks

In-app verification lives under /api/sdk/* and is the approval pathfor both money flows: initiate → approve → confirm-receipt. Your existing initiate and claim integration is unchanged; the middle step is what moves to a passkey.

It is on by default for every title. If it has been switched off for your title, the player-token mint answers 403 sdk_not_enabled_for_tenant; a 503 sdk_verification_disabled means it is off for the whole environment.

A client that cannot run a passkey is not stuck on messaging. Consoles, TVs and desktop builds use the QR device approval grant instead: the ceremony happens on the player's phone, and your server settles with the same approve call. Worked examples in curl, JavaScript and Python are on Peer Send, Cross-Game Transfer and Collecting.

On the web instead of a mobile app?

This flow assumes a mobile app with secure hardware. Web platforms get the same hardware-backed, phishing-resistant step-up using WebAuthn passkeys in the browser, an additive alternative to the device signature that lands on the exact same approval. See Platform Step-Up (WebAuthn / Passkeys).

Which method on which platform

The right verification method follows the platform the player is on. In short:

  • • Mobile app & mobile web (iOS 16+ / Android 9+) → a passkey directly (Face ID / Touch ID / fingerprint). Strongest option, zero messaging cost.
  • • Desktop web → a passkey directly (Touch ID / Windows Hello).
  • • Consoles and native desktop game clients → the QR device-approval flow, the client can’t run a passkey in-process, so the player approves on their phone. See Device Approval (Consoles & TVs).
  • • Hosted approval by email → the fallback for remote approvals with no passkey path: a signed link to a hosted page goes to the player’s oldest verified address.

How the flow works

ENROLL (once, on login)
  Your server ──(X-Game-Secret-Key)──▶ POST /api/sdk/player-token ──▶ player token ──▶ device
  Device generates a hardware keypair ─▶ POST /api/sdk/device/register  (sends the PUBLIC key only)

PER TRANSFER / SEND
  Your server starts it as normal (initiate-transfer / initiate-send).
  The device polls       GET  /api/sdk/transfers/pending   (the authoritative to-do list)
  The player taps approve; the device SIGNS the action and calls
       POST /api/sdk/transfers/{id}/approve     (self-transfer)
       POST /api/sdk/send/{id}/approve          (player-to-player send)
  For a SEND, the receiver confirms in their app:
       POST /api/sdk/send/{id}/confirm-receipt

NO PASSKEY IN THE CLIENT (console / TV / native desktop)
  Same endpoints. The factor comes from the QR device approval grant instead:
       POST /api/sdk/approvals/device/begin   -> show the QR
       POST /api/sdk/approvals/device/poll    -> "approved"
  then the SAME approve / confirm-receipt call above, with { "device_code": "..." }.

A push is not required, the device polls GET /api/sdk/transfers/pending on open and while in the foreground to discover what needs approval. That endpoint is always the source of truth.

Telling a user they have something to collect

A common question: "when a transfer or send arrives, how do I notify the logged-in user there's something to collect?"The pattern is pull for truth, push for wake-up:

In-app: poll the pending list

On app open, on foreground, and on a light interval while active (e.g. every 20 to 30s), call GET /api/sdk/transfers/pending with the player token and render its items:

  • • receiving_confirm → "You have X to collect" → route to confirm-receipt. Covers both rails: read the item's flow ("send" or "transfer") to pick the endpoint.
  • • identity_gate → "Approve your transfer/send" (your own initiated action).

Safe to drive UI directly, no raw PII (counterparty is a tenant name; amounts + currency only).

Closed app: webhook → your own push

To notify when the app isn't open, your backend subscribes to our webhooks (transfer.claim_pending and the inbound send event) and fires your ownpush (FCM / APNs / web-push). We deliver server-to-server webhooks; we don't push to devices.

The push is a wake-up only, the app must still call /pending for the authoritative list (payloads can be delayed, duplicated, or arrive out of order).

Recommendation: poll-on-open covers the in-app case with zero extra infrastructure; add the webhook→push path only when you need to alert users whose app is backgrounded or closed. Never treat a push payload as the source of truth, re-fetch /pending. See Receiving Webhooks for the event contract.

The auth model (read this first)

Never put your title’s secret key on the device

The X-Game-Secret-Key is server-side only. The device authenticates as the player, using a short-lived player token your server mints for it. A secret on the client is a leaked secret.

CredentialLives whereAuthenticatesUsed for
X-Game-Secret-KeyYour server onlyThe tenantMinting player tokens; your existing server APIs
player tokenThe player's deviceThe playerEvery /api/sdk/* call from the device (as Authorization: Bearer)

The player token is short-lived (15 minutes) and there is no refresh call. On a 401, mint a fresh one via your server's /api/sdk/player-token call and retry, never re-prompt the player just to refresh.

The endpoints

POST
/api/sdk/player-token

Server mints a short-lived player token (X-Game-Secret-Key)

Device Enrollment
POST
/api/sdk/device/register

Register the device + its hardware public key

Device Enrollment
GET
/api/sdk/transfers/pending

The authoritative list of actions awaiting this player

Approve in App
POST
/api/sdk/transfers/{id}/approve

Approve a self-transfer with a device signature

Approve in App
POST
/api/sdk/send/{id}/approve

Approve a player-to-player send (sender side)

Approve in App
POST
/api/sdk/send/{id}/confirm-receipt

Receiver confirms an incoming send

Confirm Receipt
GET
/api/transfers/{id}/status

Server-side: detect one transfer’s verification completion (X-Game-Secret-Key)

Approve in App
GET
/api/currency-sends/{id}/status

Server-side: detect one send’s verification completion (X-Game-Secret-Key)

Approve in App

Getting started

  1. Integrate the Invo plugin / SDK in your client (Unity / Unreal / native).
  2. On login, have your server mint a player token and the device register itself (Device Enrollment).
  3. Poll /api/sdk/transfers/pending and render the approve / confirm prompts (Approve in App, Collecting).
  4. For consoles, TVs and desktop builds, add the QR device approval grant. Same approve call, with the factor obtained on the player's phone.
  5. Test in sandbox. When you're ready, Invo enables in-app verification for your title, players migrate as they update.