This is the “what do I need to build?” page. Read it before you write code: it tells you which routes you own and which Authentica endpoints you call. Nothing calls you. There is no receiver route, no signature to verify, no delivery to acknowledge.

The four pieces

1

A back-channel route on your server that creates the session

Your frontend never calls Authentica. It calls your route (POST /verify/start, POST /payments/confirm, whatever the action is), and that route calls POST {BASE}/api/V1/Verify/session with HTTP Basic auth.Send an Idempotency-Key header. This is the call that costs money in SMS, and the one your HTTP client will retry on timeout — see Create session.The secret can never live in a browser. Not in a bundle, not in a mobile app, not in an env var that gets inlined at build time. If your architecture has nowhere to put a secret, you cannot integrate — you need a backend, even a tiny one.
2

Somewhere to store the pending action

Store, before you hand off: the state you generated, the sessionId from the response, the userRef you started for, and what the action actually is (payment id, new phone number, record being approved).state is how the landing page finds the action. sessionId is how GET /session/{id} finds the outcome. Store both — they are the same record. See Routing many actions through one return URL.
3

A landing route — the registered returnUrl

A plain GET page the browser is redirected to, carrying ?session_id=…&state=…. Ordinary route, ordinary cookies, nothing secret on it.Its job is not to display a result it was handed — it is to fetch one: call GET /session/{sessionId} with your credentials, check the echoed state and userRef against your record, fulfil, then set your own session and redirect.
4

One idempotent `onVerified(sessionId)` function

Write the fulfilment logic once and make it safe to call twice. The landing page can be reloaded, and a reconciliation sweep can reach the same session. See One onVerified, two callers.

The routes, concretely

You must also:
Register the return URL in the portal character-for-character — whole-string match, no trailing slash, no query string, and nothing in front of it that redirects
Send that same string as returnUrl on every POST /session — a mismatch is invalid_return_url
Leave the landing page an ordinary page — no CSRF exemption, no POST route, no cookie tricks: it is a same-site GET and your SameSite=Lax cookies arrive normally
Have a plan for the user who verifies and closes the tab: a sweep over pending records calling GET /session/{id} inside the 24 h window
Handle status: "failed" on the landing route — a failed verification returns the browser with the same query a successful one does, so arrival alone must never trigger fulfilment
There is exactly one integration shape. No inline or embedded mode (the hosted page refuses to be framed), no per-app response modes, no result data on the front channel, and nothing pushed to your server.

What each side owns

Before you call it done

The secret appears in exactly one place: your server configuration
POST /session is sent with an Idempotency-Key, and 422 / 409 responses are handled rather than retried blindly
You store sessionId alongside state and userRef
The landing route never fulfils from session_id / state alone — it always reads GET /session/{id} and matches the echoed state and userRef
The action is read from your stored record — never from query params or client-side storage
state is opaque and single-use, and no URL you redirect to ever comes out of it
onVerified(sessionId) is idempotent — a reload of the landing page performs the action once
Nothing tries to iframe the hosted page, and handoff never appears in a URL
Line-by-line pre-launch list: Go-live checklist.