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 yourclient_id or secret — it asks your backend to start the flow and gets back two values:
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 ownstate, useless without your client secret. So the same rules as web apply:
- Validate
stateagainst the pending record you stored on this attempt. - Match the returned
userRefagainst 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.
Custom scheme vs Universal / App Link
You register whichever you want as thereturnUrl; 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.
?session_id=&state=, and your backend does the same read-back.
See also
- Create session — the
platformandreturnUrlfields - Get your credentials — registering your return URL / deep link
- Frontend overview — the browser half of the flow
- Routing many actions through one return URL — the
statepattern

