The Para Flutter SDK enables you to integrate secure wallet features including creation, passkey-based authentication, and transaction signing into your mobile applications. This guide covers all necessary steps from installation to implementing authentication flows.
Prerequisites
To use Para, you need an API key. This key authenticates your requests to Para services and is essential for
integration.
Don’t have an API key yet? Request access to the Developer Portal to create API keys, manage billing, teams, and more.
Install the SDK
Start by installing the Para SDK:
Configure your app’s URL scheme for OAuth authentication flows. This enables OAuth providers to redirect back to your app after authentication.
- In Xcode, select your project in the navigator
- Select your app target
- Go to the Info tab
- Scroll down to URL Types and click + to add a new URL type
- Fill in the fields:
- URL Schemes: Enter your scheme name (e.g.,
yourapp, myflutterapp)
- Role: Select Editor
- Identifier: Use your bundle identifier or a descriptive name
Make sure your URL scheme is unique to avoid conflicts with other apps. Use a scheme related to your app’s bundle ID (e.g., com.mycompany.myapp) for uniqueness.
Configure URL scheme handling in your android/app/src/main/AndroidManifest.xml file:
- Locate your MainActivity in the AndroidManifest.xml
- Update your MainActivity with the complete configuration below:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Standard launcher intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Custom scheme deep links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>
<!-- Required for OAuth authentication flows -->
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>
Android 12+ Requirement: The android:exported="true" attribute is mandatory for apps targeting Android 12 (API 31) and higher when the activity has intent filters.
Replace yourapp with your actual app scheme. This scheme must match the appScheme parameter used in Para initialization. The android:launchMode="singleTop" prevents multiple instances of your app from being created when deep links are opened.
Enable passkeys only if you’ve turned them on in the Para Developer Portal. Configure both iOS and Android platforms:
To enable passkeys on iOS, you need to configure Associated Domains:
- Open your Flutter project’s iOS folder in Xcode
- In Xcode, go to Signing & Capabilities for your app target
- Click + Capability and add Associated Domains
- Add the following entries:
webcredentials:app.usecapsule.com
webcredentials:app.beta.usecapsule.com
- Register your Team ID + Bundle ID with Para via the Developer Portal
Without properly registering your Team ID and Bundle ID with Para, passkey authentication flows will fail. Contact Para support if you encounter issues with passkey registration.
Get SHA-256 Fingerprint
To get your SHA-256 fingerprint:
- For debug builds:
keytool -list -v -keystore ~/.android/debug.keystore
- For release builds:
keytool -list -v -keystore <your_keystore_path>
Register your package name and SHA-256 fingerprint with Para via the Developer PortalWithout properly registering your package name and SHA-256 fingerprint with Para, passkey authentication flows will fail. Contact Para support if you encounter issues with passkey registration.
Quick Testing Option: You can use com.getpara.example.flutter as your package name for immediate testing. This package name is pre-registered and works with the SHA-256 certificate from the default debug.keystore, making it testable in debug mode for newly scaffolded Flutter apps.
Fix Namespace Issue
For newer versions of Flutter, you need to add the following configuration block to your android/build.gradle file to resolve a namespace issue with the passkey dependency:subprojects {
afterEvaluate { project ->
if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
}
Device Requirements
To ensure passkey functionality works correctly:
- Enable biometric or device unlock settings (fingerprint, face unlock, or PIN)
- Sign in to a Google account on the device (required for Google Play Services passkey management)
Initialize Para
To use Para’s features, you’ll need to initialize a Para client instance that can be accessed throughout your app. This
client handles all interactions with Para’s services, including authentication, wallet management, and transaction
signing.
Create a file (e.g., lib/services/para_client.dart) to initialize your Para client:
lib/services/para_client.dart
import 'package:para/para.dart';
// Para Configuration
final config = ParaConfig(
apiKey: 'YOUR_PARA_API_KEY', // Get from: https://developer.getpara.com
environment: Environment.beta, // Use Environment.prod for production
);
// Initialize Para client instance
final para = Para.fromConfig(
config: config,
appScheme: 'yourapp', // Your app's scheme (without ://)
);
You can access para from anywhere in your app by importing the file where you initialized it. This singleton pattern
ensures consistent state management across your application.
Authenticate Users
Para provides a unified authentication experience that supports email, phone, and social login methods. The SDK automatically determines whether a user is new or existing and guides you through the appropriate flow.
Beta Testing Credentials In the BETA Environment, you can use any email ending in @test.getpara.com (like
[email protected]) or US phone numbers (+1) in the format (area code)-555-xxxx (like (425)-555-1234). Any OTP
code will work for verification with these test credentials. These credentials are for beta testing only. You can
delete test users anytime in the beta developer console to free up user slots.
Create a single FlutterWebAuthSession and reuse it whenever you call handleLogin or handleSignup.
lib/views/authentication_view.dart
final webAuthSession = FlutterWebAuthSession(
callbackUrlScheme: 'yourapp',
);
Build Your Authentication Flow
Para’s One-Click Login is the default path. As soon as you receive an AuthState, check for loginUrl and complete the inline flow before falling back to other methods. The pattern below mirrors the example app while keeping the logic compact.
Para supports authentication with both email addresses and phone numbers.Handle Email/Phone Submission
Initiate authentication with email or phone:lib/views/authentication_view.dart
// Determine if input is email or phone
final Auth auth;
if (userInput.contains('@')) {
auth = Auth.email(userInput);
} else {
auth = Auth.phone(userInput); // Include country code
}
// SDK call: Initiate authentication
final authState = await para.initiateAuthFlow(auth: auth);
// One-Click login or signup
if (authState.loginUrl?.isNotEmpty == true) {
await para.presentAuthUrl(
url: authState.loginUrl!,
webAuthenticationSession: webAuthSession,
);
final nextStage = authState.effectiveNextStage;
if (nextStage == AuthStage.signup) {
await para.waitForSignup();
} else {
await para.waitForLogin();
}
await para.touchSession();
await para.fetchWallets();
return;
}
// Handle the result based on stage (only needed for passkey/password flows)
switch (authState.stage) {
case AuthStage.verify:
// New user - show verification UI (see optional passkey/password section)
break;
case AuthStage.login:
// Existing user - fall back to passkey/password flows
await para.handleLogin(
authState: authState,
webAuthenticationSession: webAuthSession,
);
break;
case AuthStage.signup:
// Complete signup with passkey/password if you enable them
break;
}
Social login is integrated directly into the unified authentication view. Users can authenticate with Google, Apple, or Discord.Handle Social Login
Implement the social login handler:lib/views/authentication_view.dart
Future<void> handleSocialLogin(OAuthMethod provider) async {
try {
final authState = await para.verifyOAuth(
provider: provider,
appScheme: 'yourapp',
);
if (authState.loginUrl?.isNotEmpty == true) {
await para.presentAuthUrl(
url: authState.loginUrl!,
webAuthenticationSession: webAuthSession,
);
final nextStage = authState.effectiveNextStage;
if (nextStage == AuthStage.signup) {
await para.waitForSignup();
} else {
await para.waitForLogin();
}
await para.touchSession();
await para.fetchWallets();
return;
}
if (authState.stage == AuthStage.login) {
try {
await para.touchSession();
} catch (_) {
// Session refresh is best-effort
}
await para.fetchWallets();
// Navigate to main app
return;
}
} catch (e) {
// Handle error
print('OAuth login failed: ${e.toString()}');
}
}
Optional: Passkey/Password Signup
If you enable passkeys or passwords in the Para dashboard, you’ll need to handle the verification stage and call handleSignup.
lib/views/authentication_view.dart
final verifiedState = await para.verifyOtp(otp: userCode);
// Reuse the One-Click block shown above here, then fall back to handleSignup.
await para.handleSignup(
authState: verifiedState,
signupMethod: SignupMethod.passkey, // or SignupMethod.password
webAuthenticationSession: webAuthSession,
);
Returning Users
Existing users follow the same One-Click-first flow. If you’ve enabled passkey or password methods, fall back to handleLogin
after the One-Click block. You can also call loginWithPasskey directly when you know a user has registered one.
Check Authentication Status
You can check if a user is already authenticated:
lib/views/content_view.dart
final isLoggedIn = await para.isSessionActive().future;
if (isLoggedIn) {
// User is authenticated, proceed to main app flow
} else {
// Show login/signup UI
}
Sign Out Users
To sign out a user and clear their session:
lib/views/settings_view.dart
await para.logout().future;
Create and Manage Wallets
After successful authentication, you can perform wallet operations:
lib/views/wallet_view.dart
// Get all user wallets
await para.fetchWallets(); // Ensure we have the latest wallets
final wallets = await para.fetchWallets().future;
if (wallets.isEmpty) {
// No wallets, perhaps create one
final wallet = await para.createWallet(
type: WalletType.evm,
skipDistribute: false,
).future;
print('Created wallet: ${wallet.address}');
// Sign a simple message (SDK handles Base64 encoding internally)
final signature = await para.signMessage(
walletId: wallet.id,
messageBase64: base64Encode(utf8.encode('Hello, Para!')),
);
print('Signature: ${(signature as SuccessfulSignatureResult).signedTransaction}');
} else {
// Use existing wallet
final firstWallet = wallets.first;
// Sign a simple message (SDK handles Base64 encoding internally)
final signature = await para.signMessage(
walletId: firstWallet.id,
messageBase64: base64Encode(utf8.encode('Hello, Para!')),
);
print('Signature: ${(signature as SuccessfulSignatureResult).signedTransaction}');
}
For detailed transaction signing with specific blockchains (EVM, Solana, Cosmos), please refer to the respective blockchain integration guides.
Example
For a complete implementation example, check out our Flutter SDK example app:
Next Steps
After integrating Para into your Flutter app, you can explore other features and integrations to enhance your Para experience.