Uplink Handoff
Overview
The Uplink Handoff flow lets you execute automation within the session that Transact creates after a user finishes authenticating with their institution. Once authentication completes, you hide the Transact UI and connect to the same underlying session through the @uplink-code/uplink JS SDK to run any automation you'd like against the user's logged-in session.
The flow has three phases:
- Create an Uplink session from your backend and pass its URL into the Atomic SDK on the mobile device.
- Present Transact. When you receive an
onAuthStatusUpdateevent with theauthenticatedstatus, hide Transact so the user is returned to your app. - Connect to the Uplink session from your JS runtime, grab the page Transact created, and drive it with the Uplink Page API.
Requirements
- A secured backend running a JavaScript runtime (Node.js, Bun, Deno) where you can run the @uplink-code/uplink npm package. Your Uplink API key must stay on the server and never reach a client device.
- An Uplink API key, which you can create at console.uplink.build.
- The Atomic Transact iOS SDK version 3.33.0 or later integrated in your app. See the Transact SDK reference for setup instructions.
Create the Uplink Session
From your secured backend, create an Uplink session and deliver its sessionUrl to your mobile app. Your Uplink API key must never be exposed to a client device, so all calls to the JS SDK that take an API key need to run server-side.
- Install the SDK: Install
npm install @uplink-code/uplink - Call
uplink.session. To have Uplink generate the signing and encryption keys for you, setinclude.ecdhandinclude.ecdsatotrue— otherwise omit them and provide your own keys when you connect later. You can optionally supplyrestrict.verifier; if you don't, Uplink generates a verifier for you and returns it onsession.credential. - Persist the returned
sessionobject. The simplest path is to store the full object server-side — it contains the private keys and the credential you'll need to connect later. If you'd rather store only thesessionId, you must supply your ownrestrict.verifieron the call above and remember it yourself, because the verifier is never returned from the Uplink API again. - Send
session.sessionUrlto your iOS app over your own transport (push, REST response, websocket — whatever you already use).
import uplink from '@uplink-code/uplink'
const session = await uplink.session(apiKey, {
include: { ecdh: true, ecdsa: true }
// Optional — only needed if you plan to persist just session.sessionId:
// restrict: { verifier: crypto.randomUUID() }
})
// Persist the full `session` object (recommended), or at minimum
// session.sessionId plus the verifier you supplied above.
// Then deliver session.sessionUrl to your mobile app.credential like a secret. Anyone who has both the sessionUrl and the credential can connect to and drive the session. Present Transact
On the mobile device, build an AtomicConfig with the uplinkSessionUrl set to the value you received from your backend, then present Transact as you normally would. When the user finishes authenticating with their institution, Transact fires the onAuthStatusUpdate callback with status .authenticated. At that point, call Atomic.hideTransact() to dismiss the Transact UI and return the user to your app. The underlying session stays alive so your automation can take it over.
import AtomicTransact
let config = AtomicConfig(
publicToken: publicToken,
scope: .userLink,
tasks: [.init(product: .deposit)],
uplinkSessionUrl: uplinkSessionUrl // session.sessionUrl from your backend
)
Atomic.presentTransact(
from: self,
config: config,
onAuthStatusUpdate: { update in
if update.status == .authenticated {
// User has finished authenticating. Hide the Transact UI to
// return the user to your app; the session stays alive so your
// automation can take it over.
Atomic.hideTransact()
}
}
)Connect and Automate
Back on your secured backend, connect to the same Uplink session and grab the page Transact already created. uplink.client.fromSession imports the session's keys and credential and returns a connected client. This step must also run server-side — the session's private keys and your API key must never be exposed to a client device.
How you reconstruct the session argument depends on how you persisted it in step one:
- If you stored the full session object from
uplink.session(...), pass it straight tofromSession. The credential is already on the object — no verifier needed. - If you only stored the
sessionId, you must have supplied your ownrestrict.verifierback in step one. Fetch a fresh session withuplink.getSession(asking for both keys) and then attach thatverifieronto thecredentialfield before callingfromSession. The Uplink API never returns the credential, so you must keep it yourself.
import uplink from '@uplink-code/uplink'
// Option A — you persisted the full session object from step one:
const client = await uplink.client.fromSession(session)
// Option B — you only persisted sessionId + verifier:
const fresh = await uplink.getSession(apiKey, sessionId, {
include: { ecdh: true, ecdsa: true }
})
fresh.credential = verifier // the verifier you chose in step one
const client = await uplink.client.fromSession(fresh)
// Grab the page Transact created on the user's device:
const browsers = await client.browsers()
const browser = browsers[0]
const pages = await browser.pages()
const page = pages[0]
// `page` is yours — drive it with the Uplink Page API. You now hold a page object pointing at the user's authenticated session. Refer to the Uplink Page API reference for everything you can do with it — navigation, evaluation, clicks, form input, and more.