kenal_ekyc_sdk
A Flutter SDK for integrating Kenal's eKYC solution in your applications.
Installation
Add the SDK package to your pubspec.yaml:
dependencies:
kenal_ekyc_sdk: ^1.1.0
Then run:
flutter pub get
Usage
Initializing the SDK
import 'package:kenal_ekyc_sdk/kenal_ekyc_sdk.dart';
await KenalClient.initialize(
KenalClientConfig(
apiKey: "YOUR_API_KEY",
environment: "sandbox", // or 'production'
),
);
Unified eKYC Service (Recommended)
The Ekyc service provides a unified interface for all document types including Passport, MyKad, MyPR, MyTentera, and AML checks. This is the recommended approach for new integrations.
Starting Verification
import 'package:flutter/material.dart';
import 'package:kenal_ekyc_sdk/kenal_ekyc_sdk.dart';
class EkycScreen extends StatefulWidget {
const EkycScreen({super.key});
@override
State<EkycScreen> createState() => _EkycScreenState();
}
class _EkycScreenState extends State<EkycScreen> {
String? verificationUrl;
Future<void> startVerification() async {
try {
final result = await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "JOHN DOE",
idNumber: "990101011234",
// Add other optional parameters based on document type
),
);
setState(() {
verificationUrl = result.fullURL;
});
print("Verification started: $result");
} catch (error) {
print("Verification error: $error");
// Show error dialog
}
}
@override
Widget build(BuildContext context) {
if (verificationUrl != null) {
return KenalWebView(
url: verificationUrl!,
onComplete: (data) => print("Verification completed: $data"),
onError: (error) => print("WebView error: $error"),
);
}
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: startVerification,
child: const Text('Start Verification'),
),
),
);
}
}
Parameters by Document Type
Required for All Document Types
refId(String, required) - Your unique reference identifier for this verification
Passport Verification
await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "JOHN DOE", // Required
idNumber: "A12345678", // Required (Passport number)
docIssueCountry: "USA", // Required (ISO 3166-1 alpha-3 code)
),
);
Required Fields:
name- Full name as on passport (always required)idNumber- Passport number (always required)docIssueCountry- Country that issued the passport (always required)
MyKad Verification
await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "AHMAD BIN ABDULLAH", // Required if AutofillFromOCR disabled
idNumber: "990101011234", // Required if AutofillFromOCR disabled
),
);
Required Fields:
name- Full name as on MyKad (required only if AutofillFromOCR is disabled)idNumber- 12-digit MyKad number (required only if AutofillFromOCR is disabled)
Note: If your workflow has AutofillFromOCR enabled, these fields are optional and will be extracted from the ID card.
MyPR Verification
await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "TAN AH KOW", // Required if AutofillFromOCR disabled
idNumber: "A1234567", // Required if AutofillFromOCR disabled (MyPR number)
),
);
Required Fields:
name- Full name as on MyPR (required only if AutofillFromOCR is disabled)idNumber- MyPR number (required only if AutofillFromOCR is disabled)
MyTentera Verification
await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "MOHD FAIZAL", // Required if AutofillFromOCR disabled
idNumber: "850315021234", // Required if AutofillFromOCR disabled
armyNumber: "ATM123456", // Required if AutofillFromOCR disabled
),
);
Required Fields:
name- Full name (required only if AutofillFromOCR is disabled)idNumber- IC number (required only if AutofillFromOCR is disabled)armyNumber- Military service number (required only if AutofillFromOCR is disabled)
AML Check Only
await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "JOHN DOE", // Required
birthdate: "1985-06-20", // Optional
nationality: "MYS", // Optional
),
);
Required Fields:
name- Full name (always required for standalone AML checks)
Note: For AML checks, other fields like birthdate, nationality, and idNumber are optional but recommended for more accurate results.
Generating Recovery Links
Recovery links allow users to resume incomplete verifications. You can generate them using the validationId from a previous verification.
Future<void> generateRecoveryLink(String validationId) async {
try {
final recovery = await Ekyc.generateRecoveryLink(
GenerateRecoveryLinkParams(
validationId: validationId,
),
);
// Open the recovery link in KenalWebView
setState(() {
verificationUrl = recovery.fullURL;
});
} catch (error) {
print("Recovery link error: $error");
}
}
Response:
class GenerateRecoveryLinkData {
final String token; // JWT token for the recovery session
final String fullURL; // Full URL to resume verification
final int expiresAt; // Unix timestamp in milliseconds when link expires
}
Important Notes:
- Recovery links expire after 1 hour
- The recovery link allows users to continue from where they left off
Complete Example with Recovery Link
Bringing it together in a minimal form:
// Start a verification
final start = await Ekyc.startVerification(
EkycStartParams(
refId: "customer-12345",
name: "JOHN DOE",
),
);
// Later, using a validationId obtained from Kenal callback or your backend
final recovery = await Ekyc.generateRecoveryLink(
GenerateRecoveryLinkParams(
validationId: "your-validation-id",
),
);
print(recovery.fullURL); // URL to resume verification
print(recovery.token); // Recovery token
print(recovery.expiresAt); // Expiry time in milliseconds
Legacy Document-Specific Services
The following services are maintained for backward compatibility. For new integrations, use the unified Ekyc service above.
MyKad Verification
import 'package:flutter/material.dart';
import 'package:kenal_ekyc_sdk/kenal_ekyc_sdk.dart';
class MyKadScreen extends StatefulWidget {
const MyKadScreen({super.key});
@override
State<MyKadScreen> createState() => _MyKadScreenState();
}
class _MyKadScreenState extends State<MyKadScreen> {
String? ekycUrl;
@override
void initState() {
super.initState();
_initializeClient();
}
Future<void> _initializeClient() async {
await KenalClient.initialize(
KenalClientConfig(
apiKey: "YOUR_API_KEY",
environment: "sandbox",
),
);
}
Future<void> startVerification() async {
try {
final result = await MyKad.start(
MyKadParams(
name: 'JOHN DOE',
idNumber: '900101014321',
refId: 'REF12345',
useOCRForData: false, // Set to true to use OCR for data extraction
),
);
setState(() {
ekycUrl = result.fullURL;
});
} catch (error) {
print("Verification error: $error");
}
}
@override
Widget build(BuildContext context) {
if (ekycUrl != null) {
return KenalWebView(
url: ekycUrl!,
onComplete: (data) {
print("Verification completed: $data");
setState(() => ekycUrl = null);
},
onError: (error) => print("WebView error: $error"),
);
}
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: startVerification,
child: const Text('Start MyKad Verification'),
),
),
);
}
}
MyKad Parameters:
name- Full name (required ifuseOCRForDatais false)idNumber- 12-digit MyKad number (required ifuseOCRForDatais false)refId- Your unique reference ID (required)useOCRForData- Set totrueto extract data via OCR (optional, default: false)
Passport Verification
import 'package:flutter/material.dart';
import 'package:kenal_ekyc_sdk/kenal_ekyc_sdk.dart';
class PassportScreen extends StatefulWidget {
const PassportScreen({super.key});
@override
State<PassportScreen> createState() => _PassportScreenState();
}
class _PassportScreenState extends State<PassportScreen> {
String? ekycUrl;
@override
void initState() {
super.initState();
_initializeClient();
}
Future<void> _initializeClient() async {
await KenalClient.initialize(
KenalClientConfig(
apiKey: "YOUR_API_KEY",
environment: "sandbox",
),
);
}
Future<void> startVerification() async {
try {
final result = await Passport.start(
PassportParams(
name: 'JOHN DOE',
passportNumber: 'A12345678',
docIssueCountry: 'USA',
refId: 'REF12345',
),
);
setState(() {
ekycUrl = result.fullURL;
});
} catch (error) {
print("Verification error: $error");
}
}
@override
Widget build(BuildContext context) {
if (ekycUrl != null) {
return KenalWebView(
url: ekycUrl!,
onComplete: (data) {
print("Verification completed: $data");
setState(() => ekycUrl = null);
},
onError: (error) => print("WebView error: $error"),
);
}
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: startVerification,
child: const Text('Start Passport Verification'),
),
),
);
}
}
Passport Parameters:
name- Full name as on passport (required)passportNumber- Passport number (required)docIssueCountry- Country that issued the passport (required)refId- Your unique reference ID (required)
API Reference
KenalClient
Initializes the SDK with your API key and environment settings.
await KenalClient.initialize(
KenalClientConfig(
apiKey: "YOUR_API_KEY",
environment: "sandbox", // or "production"
),
);
Ekyc
Unified service for all document types and AML checks.
Ekyc.startVerification(params)
Starts a new eKYC verification process.
Returns: Future<EkycStartData>
class EkycStartData {
final String token; // JWT token for the verification session
final String fullURL; // Full URL to open in KenalWebView
}
Ekyc.generateRecoveryLink(params)
Generates a recovery link for an existing verification session.
Returns: Future<GenerateRecoveryLinkData>
class GenerateRecoveryLinkData {
final String token; // JWT token for the recovery session
final String fullURL; // Full URL to resume verification
final int expiresAt; // Unix timestamp (seconds) when link expires
}
MyKad (Legacy)
Static service for MyKad verification.
Future<MyKadData> MyKad.start(MyKadParams params)
Returns: MyKadData with fullURL property.
Passport (Legacy)
Static service for Passport verification.
Future<PassportData> Passport.start(PassportParams params)
Returns: PassportData with fullURL property.
KenalWebView
Widget for displaying the eKYC verification flow.
KenalWebView(
url: String, // Required: verification URL
onComplete: (data) {}, // Called when verification completes
onError: (error) {}, // Called on error
onExceedMaxRetries: (data) {}, // Called when user exceeds max retries
onMessage: (message) {}, // Called for custom messages
loader: Widget?, // Optional: custom loading widget
)
Permissions Setup
To ensure the eKYC flow works correctly, you need to set up the necessary permissions for camera access in both Android and iOS platforms.
Android (AndroidManifest.xml) android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:label="kenal_ekyc_demo_flutter"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
// Other configurations...
<provider
android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider"
android:authorities="${applicationId}.flutter_inappwebview_android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
iOS (Info.plist)
<key>NSCameraUsageDescription</key>
<string>Camera is required for eKYC verification</string>