flex_sdk is FLEX’s embeddable Flutter SDK for host apps that want a ready-made wallet experience. You can use it to launch PayTag onboarding and login, return a customer token to your app, or open the full wallet dashboard with transfers, transaction history, and QR scanning.
Use this guide if you are integrating from Flutter and want FLEX to handle some or all of the customer-facing wallet UI for you.
Launch the full wallet
Use
FlexSdk.openWithConfig() to open onboarding, login, wallet, transfers, and QR flows.Get a customer token
Use
FlexSdk.authenticate() or FlexSdk.login() when your app will render its own UI after authorization.Jump to QR scanning
Use
FlexSdk.scanQR() to open the scanner directly while reusing the same stored session.Need raw APIs instead?
Use the Wallet SSO API guides when you want to build the wallet experience yourself.
This page complements the Wallet SSO API docs. If your team is building the UI in Flutter from scratch, start with the SSO overview, authentication guide, and integration guide.
1. Requirements
The host app’s own
pubspec.yaml environment.sdk constraint must be
compatible with ^3.9.0.
Key transitive dependencies
flex_sdk pulls in go_router: ^17.0.0, flutter_riverpod: ^2.4.9, dio,
shared_preferences, jwt_decoder, lottie, flutter_svg, pinput,
oktoast, local_auth, share_plus, url_launcher, among others. If your
host app already depends on any of these (especially go_router or
flutter_riverpod), make sure your version constraints overlap with the
ones above - flutter pub get will fail with a version-solving error
otherwise.
2. Installation
flex_sdk is not published to pub.dev. It’s distributed as a versioned
local package - you get a zip, you point pubspec.yaml at the unzipped
folder. No git access, no server, no auth token, nothing to invite anyone
to. A git dependency remains available as an alternative for teams that do
have repo access (§2.2).
2.1 For partners: local package handoff (recommended)
The FLEX team hands you aflex_sdk-[version].zip (built via
tool/package_for_local_dist.sh - see below). To use it:
-
Unzip it somewhere on your machine, e.g.
~/sdks/flex_sdk-1.0.0/. -
Point your
pubspec.yamlat that folder: -
flutter pub get.
~/sdks/flex_sdk-1.1.0/) and update the path: line to point at it.
⚠️ The zip bundles everything the package needs to build except.env.staging/.env.prod(shipped as empty placeholders - the SDK doesn’t need real values in them unless you’re calling the deprecatedFlexSdk.open()/initialize()fallback instead of passing aFlexSdkConfig, which isn’t the documented path). Your ownclientId/apiKeystill go throughFlexSdkConfig- see §4.
For the FLEX team: produce a release zip
pubspec.yaml, snapshots the tracked source, regenerates and includes the *.g.dart and *.freezed.dart files, writes placeholder env files, validates the packaged copy on its own, and outputs dist/flex_sdk-[version]/ plus dist/flex_sdk-[version].zip.
Send the .zip to the partner. The adjacent folder is only the unpacked build artifact. Commit pending changes first because the script packages the last committed tracked files, not uncommitted edits.
2.2 Git dependency (alternative, if you have repo access)
- If the repo is private, grant the consuming team access (e.g. a deploy
key or a GitHub team invite) -
flutter pub getuses your normal git credentials/SSH agent under the hood, same asgit clone. - Prefer pinning
ref:to a tag (v1.0.0) over a branch (main) so a host app’s build doesn’t silently change when this repo gets new commits.
2.3 Working on flex_sdk itself
If you’re developingflex_sdk alongside a host app in the same local
checkout (e.g. this repo’s own momo_test_app test harness), a path:
dependency straight at the live source is simplest:
flutter pub get
without any packaging step.
After adding or upgrading the dependency (any of the above), do a full
flutter clean + rebuild (not just hot reload/hot restart) at least
once. The SDK bundles its own image/SVG/Lottie assets under its own
package namespace, and a stale build can fail to pick up newly-added
assets.
Required native setup (host app)
flex_sdk is a Flutter package, not a plugin - its own
android/AndroidManifest.xml and ios/Info.plist are only used when running
the SDK’s repo standalone. They are not auto-merged into your host app’s
build. Because the SDK uses biometric authentication (local_auth) for
transaction confirmation, and the camera (mobile_scanner) for QR scanning
(FlexSdk.scanQR(), or reaching the scanner from inside the full dashboard),
your host app must declare the following itself:
Android - android/app/src/main/AndroidManifest.xml:
ios/Runner/Info.plist:
INTERNET is implicit on iOS.) Skip USE_BIOMETRIC/NSFaceIDUsageDescription
if you only ever use FlexSdk.login()/FlexSdk.authenticate() and never
trigger a transaction confirmation inside the dashboard; skip
CAMERA/NSCameraUsageDescription if you never call FlexSdk.scanQR() or
FlexSdk.openWithConfig() - but it costs nothing to add all of it up front.
⚠️ Android also requiresMainActivityto extendFlutterFragmentActivity, notFlutterActivity.local_auth’s biometric prompt is aFragmentManager-hostedBiometricPrompt, and it cannot attach to a plainFlutterActivity-flutter createscaffoldsFlutterActivityby default, so most host apps need this changed. If you skip this, biometrics fails silently-ish with"Unable to show the authentication prompt right now"instead of an obvious crash, which makes it easy to miss. In your host app’sandroid/app/src/main/kotlin/.../MainActivity.kt(or.java):There is no iOS equivalent -local_auth’sLAContext-based prompt works with the defaultFlutterViewControlleras-is.
3. Four ways to launch the SDK
flex_sdk exposes four entry points depending on how much of the product
you want to embed and whether the user already has a session.
3.1 FlexSdk.openWithConfig() - full experience
Launches the complete FLEX experience: registration/login, the account
dashboard, wallet funding, sending money, transaction history - everything.
Use this when you want FLEX to be the account experience inside your app.
openWithConfig returns Future<void> - it just opens the flow as a new
full-screen route on top of your current screen (Navigator.push). It does
not hand anything back to you; the user stays inside the SDK’s UI (dashboard,
etc.) until they navigate back out on their own.
Screens shown, in order, on first use: CreatePayTag (BVN entry) →
verification method → OTP → preview/complete registration → success →
auto-login → AuthorizeAccount → dashboard (LandingPage). If a valid
session already exists on-device, it skips straight to the dashboard after
a brief splash screen.
Optional bvn param - if your host app already collects the user’s BVN
itself (e.g. it’s part of your own onboarding form), pass it in to skip the
SDK’s BVN entry screen:
- Omitted / empty (default): unchanged - the flow opens on
CreatePayTagand the user types their BVN there. - Provided: must be exactly 11 digits, or
openWithConfigthrows anArgumentErrorsynchronously (before any navigation happens). When valid, the SDK skipsCreatePayTagentirely, makes the same tag-creation API call that page would have made on the user’s behalf, and opens the flow directly on the OTP verification screen - the OTP has already been dispatched to the user’s registered contact by that point, so they land straight on the pin entry step.
3.2 FlexSdk.authenticate() - token-only
Launches only the login/registration + authorize screens - no
dashboard, no transactions. Once the user is authenticated, resolves with a
token your app can use to call FLEX’s APIs directly from your own
backend/web layer.
CreatePayTag (with an “Already have one?
Continue” link to the login screen) or direct login → AuthorizeAccount.
As soon as authorization succeeds, the SDK closes its own screens and
resolves the Future with the token - there’s no dashboard, no account
summary screen, nothing further shown. If a valid session token already
exists on-device, the call resolves immediately with that token and no SDK
UI is shown at all.
3.3 FlexSdk.scanQR() - jump straight to QR scanning
Opens the full dashboard flow’s QR scanner directly, skipping the rest of
the dashboard. In the background, the SDK checks on-device for an existing
session:
- Session exists → the QR scanner (
ScanQrPage) opens immediately. - No session → the user is sent to the PayTag login screen
(
VerifyPayTagPage) first. Once they log in and authorize, they land on the QR scanner automatically - no extra call needed on your side.
scanQR returns Future<void>, same shape as openWithConfig() - it opens
the flow as a new full-screen route and doesn’t hand anything back to you.
Since it uses the same full-flow router as openWithConfig(), the user can
navigate anywhere else in the dashboard from there (back button, etc.) -
this just controls where the flow starts.
3.4 FlexSdk.login() - token-only, starting at login
Same token-only contract as authenticate() (§3.2), but skips the
registration screen entirely: an unauthenticated user is sent straight to
the PayTag login screen (VerifyPayTagPage) instead of CreatePayTag.
- No session →
VerifyPayTagPage(login) →AuthorizeAccount→ resolves with the token. - Session exists → resolves immediately with the token, no UI shown.
login() when you know the user already has a FLEX PayTag and want to
skip past the “create one” registration screen; use authenticate() when
they might be new.
4. Configuration reference
FlexSdkConfig
Built internally from the named parameters you pass to openWithConfig/
authenticate, but also constructible directly via the helpers below if you
want to prepare a config ahead of time:
Both credentials are sent as request headers (
x-clientId, x-apiKey) on
every API call the SDK makes - get the right pair for the environment
you’re targeting, staging and production keys are not interchangeable.
⚠️ As of this writing, the SDK’s own networking layer (dio_factory.dart) actually sends these asr-partner-id/r-api-key, notx-clientId/x-apiKey. Documenting the header names you asked for here rather than silently changing the code - ifx-clientId/x-apiKeyis what the backend actually expects,dio_factory.dartneeds a matching code change before this doc and the SDK agree with each other.
FlexSdkEnvironment
Each environment maps internally to a fixed base URL (in
flex_sdk_config.dart) - you don’t pass one yourself, just pick
.staging or .production. Contact the FLEX integrations team to confirm
which hosts they resolve to for your engagement.
hostAppName
Optional, defaults to 'FLEX'. Available on both openWithConfig() and
authenticate()/login(). Shown on the authorize/consent screen as
“Authorize ”. Pass your own app’s display name so the consent
step doesn’t say “Authorize FLEX” inside a third-party app.
Theming
OptionalprimaryColor (a Color), available on both entry points.
When set, the SDK uses it as a solid brand color everywhere it would
otherwise use its own default look:
- Text links (e.g. “Create one”, “terms & conditions”, “Resend”)
CustomTextFieldfocus bordersAppButton’s fill (replacing its default gradient)- Checkbox/checkmark accents
- Icon tints on the verification-method screen
- The splash/loading screen’s
CircularProgressIndicator
null, the default) to keep the SDK’s own default look -
including a couple of two-stop gradients (the button fill/glow, the
verification-method card borders) that only apply when no primaryColor
is set, since a gradient can’t be built from a single passed-in color.
FlexSdkAuthResult
What authenticate() resolves with on success:
authenticate() returns Future<FlexSdkAuthResult?> - treat null as “no
session” (user backed out or logged out), not as an error.
5. Session persistence & logout
The SDK persists the session token, decoded user profile, and paytag toSharedPreferences on-device once a user completes login/authorize. Every
entry point checks for that stored session in the background before
deciding where to start the user:
If
openWithConfig() is called with a valid bvn, the “no session” path
starts at the OTP screen instead of CreatePayTag - see §3.1.
authenticate()/login() don’t leave any SDK screen open once they
resolve - there’s nothing to log out of on that path. Your own app is free
to clear Strings.token/Strings.userInfo/Strings.paytag from
SharedPreferences (see §7) whenever you want to end
that session; the next authenticate()/login() call will then start
fresh at registration/login again.
openWithConfig()/scanQR() are different - they open the full dashboard,
which has its own “Go back to ” button (bottom of
LandingPage). That button always clears the stored session (token,
profile, paytag) and returns control to your app.
6. Error handling
Network/API failures inside the SDK’s own screens are surfaced to the user via in-SDK snackbars/toasts - you don’t need to handle them yourself while the SDK’s UI is on screen. The only thing your app needs to handle on its own side is:authenticate()resolving tonull(no session) vs. a populatedFlexSdkAuthResult.FlexSdkConfig.validate()throwingArgumentErrorif you construct a config directly with an emptyclientId/apiKey(both entry-point methods call this for you internally, so a bad credential fails fast rather than making a request).openWithConfig()throwingArgumentErrorsynchronously ifbvnis passed but isn’t exactly 11 digits - no navigation happens in this case, so wrap the call in atry/catchifbvncomes from user input you haven’t already validated.openWithConfig()’s tag-creation API call, whenbvnis passed, happens before any SDK screen is on-screen - unlike other in-flow failures, a failure here has no snackbar to surface it and instead rethrows out of the awaitedFuture. Wrap the call intry/catchand show your own error UI if you use thebvnshortcut.
7. What’s exported
import 'package:flex_sdk/flex_sdk.dart'; gives you:
FlexSdk- the four entry points (openWithConfig,authenticate,login,scanQR), plusstaging()/production()config helpersFlexSdkConfig,FlexSdkEnvironmentFlexSdkAuthResultStrings- theSharedPreferenceskey constants the SDK persists the session under (Strings.token,Strings.userInfo,Strings.paytag). Exported so your own app can check(prefs.getString(Strings.token) ?? '').isNotEmptyto know whether a session exists, without hardcoding the raw key string or waiting for one of the four entry points to tell you. The SDK and host app share the same on-deviceSharedPreferencesstore, so this reads live.AppColors,AppTheme- the SDK’s own color/theme constants, exported for reference (e.g. if you want visual continuity elsewhere in your app). Note: there is currently no way to pass your own theme/colors into the SDK’s screens - they always render with FLEX’s own fixed palette.AppRoute- the SDK’s internal full-flow router. You shouldn’t need to touch this directly; it’s wired up for you byopenWithConfig().