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'sflow("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.
| Credential | Lives where | Authenticates | Used for |
|---|---|---|---|
| X-Game-Secret-Key | Your server only | The tenant | Minting player tokens; your existing server APIs |
| player token | The player's device | The player | Every /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
/api/sdk/player-tokenServer mints a short-lived player token (X-Game-Secret-Key)
/api/sdk/device/registerRegister the device + its hardware public key
/api/sdk/transfers/pendingThe authoritative list of actions awaiting this player
/api/sdk/transfers/{id}/approveApprove a self-transfer with a device signature
/api/sdk/send/{id}/approveApprove a player-to-player send (sender side)
/api/sdk/send/{id}/confirm-receiptReceiver confirms an incoming send
/api/transfers/{id}/statusServer-side: detect one transfer’s verification completion (X-Game-Secret-Key)
/api/currency-sends/{id}/statusServer-side: detect one send’s verification completion (X-Game-Secret-Key)
Getting started
- Integrate the Invo plugin / SDK in your client (Unity / Unreal / native).
- On login, have your server mint a player token and the device register itself (Device Enrollment).
- Poll
/api/sdk/transfers/pendingand render the approve / confirm prompts (Approve in App, Collecting). - For consoles, TVs and desktop builds, add the QR device approval grant. Same approve call, with the factor obtained on the player's phone.
- Test in sandbox. When you're ready, Invo enables in-app verification for your title, players migrate as they update.