Atomic logo

Uplink Handoff

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:

  1. Create an Uplink session from your backend and pass its URL into the Atomic SDK on the mobile device.
  2. Present Transact. When you receive an onAuthStatusUpdate event with the authenticated status, hide Transact so the user is returned to your app.
  3. Connect to the Uplink session from your JS runtime, grab the page Transact created, and drive it with the Uplink Page API.
  • 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.
Android support is coming soon. The handoff flow is currently documented for iOS only.

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.

  1. Install the SDK:
    Install
    npm install @uplink-code/uplink
  2. Call uplink.session. To have Uplink generate the signing and encryption keys for you, set include.ecdh and include.ecdsa to true — otherwise omit them and provide your own keys when you connect later. You can optionally supply restrict.verifier; if you don't, Uplink generates a verifier for you and returns it on session.credential.
  3. Persist the returned session object. 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 the sessionId, you must supply your own restrict.verifier on the call above and remember it yourself, because the verifier is never returned from the Uplink API again.
  4. Send session.sessionUrl to your iOS app over your own transport (push, REST response, websocket — whatever you already use).
Create the session
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.
Treat the credential like a secret. Anyone who has both the sessionUrl and the credential can connect to and drive the session.

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.

iOS — present Transact with Uplink handoff
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()
        }
    }
)
Android support is coming soon. The handoff flow currently requires the iOS Transact SDK.

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 to fromSession. The credential is already on the object — no verifier needed.
  • If you only stored the sessionId, you must have supplied your own restrict.verifier back in step one. Fetch a fresh session with uplink.getSession (asking for both keys) and then attach that verifier onto the credential field before calling fromSession. The Uplink API never returns the credential, so you must keep it yourself.
Connect to the session and drive the page
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.