A native mobile app integrates exactly like the web flow — your backend makes the two authenticated calls (POST /session to start, GET /session/{id} to read the outcome) — with one difference: the return is delivered to a deep link your app registers, instead of an ordinary web page. Authentica just redirects to that registered returnUrl with ?session_id=…&state=…; the OS routes it to your app.

What your app needs from your backend

Nothing on this page is an Authentica call. Your app never creates a session and never sees your client_id or secret — it asks your backend to start the flow and gets back two values:
That is the whole contract between your app and your server. Your backend produces those by calling POST /session with platform: "mobile" and your registered deep link as returnUrl — see Create session for the request shape, and Get your credentials for registering the deep link.
On a mobile session the verifyUrl comes back GET-openable (binding: "get"), e.g. .../verify/open?hx=…. Your app just opens it in a Custom Tab — no form, no POST, no WebView. It carries the single-use hand-off and auto-submits it for you server-side, then runs the normal flow. On a web session the same call returns binding: "form_post" and the page is reached by POSTing hx — the difference is handled for you by platform.
platform: "mobile" is what allows returnUrl to be a custom-scheme deep link (myapp://…) instead of an http/https URL, so the OS can route the return into your app. Your backend sets it — an app cannot, because it rides on the authenticated call. Registration is an exact, whole-string match, exactly like a web return URL.

The flow

1

Open the hosted verify page

From your app, open the verifyUrl from your mobile session in the system browser — an Android Custom Tab or an iOS SFSafariViewController — with a plain GET. It is already a launch URL (binding: "get"): it carries the single-use handoff and submits it for you, so there is no form to build and no POST to make. Use the system browser, not a WebView you control — the system browser keeps device recognition and passkeys working.
2

User verifies

The user completes OTP (or a passkey) on the hosted page. Nothing secret is ever handed to the app.
3

Authentica redirects to your deep link

On completion Authentica redirects to your registered returnUrl with the return appended: myapp://verify/callback?session_id=vs_…&state=…. This is the same ?session_id=&state= as the web return — only the URL scheme differs.
4

The OS hands it to your app

The OS routes the deep link to your app. Read session_id and state off it.
5

Your app calls YOUR backend

POST session_id and state to your backend. Your backend does the authenticated GET /session/{id} read-back — exactly like web — and tells the app what to do next.

Trust rules (unchanged)

The deep-link values are non-secret identifiers — an id and your own state, useless without your client secret. So the same rules as web apply:
  • Validate state against the pending record you stored on this attempt.
  • Match the returned userRef against the user you started for.
  • Read the outcome only from the authenticated GET /session/{id} on your backend — never trust the query string as evidence.
Never put your client secret in the app. The read-back stays on your server. You register whichever you want as the returnUrl; Authentica just redirects to it.
  • Custom scheme (myapp://…) — simplest to set up, works everywhere, but any app on the device can register the same scheme, so treat the return purely as a lookup key (which the trust rules above already do).
  • Universal Link (iOS) / App Link (Android) — an https:// link tied to a domain you control via a site-association file. More secure (only your app can claim it) but more setup. Use it when you want the OS to guarantee the return reaches your app.
Either way the return carries the same ?session_id=&state=, and your backend does the same read-back.

See also