Real-time Events — Integration
Workflow setup (step configuration)
To support real-time milestone tracking, clients must configure stepIds in their workflow configuration.
What is a stepId?
A stepId identifies a logical grouping of modules (e.g., DigiLocker, Bank Account Verification). When a step completes, the SDK emits a step_ended event with the associated stepId.
How can I configure it?
Let's say your workflow looks like:
Module X → Module 1 → Module 2 → Module 3 → Module 4 → Module Y
If Modules 1 through 4 belong to a logical step (e.g., digilocker), then add the same stepId (e.g., "digilocker") to each of them.
Sample workflow JSON
[
{ "id": "module_x", "type": "typeX", "properties": {}, "variables": [] },
{ "id": "module_1", "type": "type1", "stepId": "digilocker", "properties": {}, "variables": [] },
{ "id": "module_2", "type": "type2", "stepId": "digilocker", "properties": {}, "variables": [] },
{ "id": "module_3", "type": "type3", "stepId": "digilocker", "properties": {}, "variables": [] },
{ "id": "module_4", "type": "type4", "stepId": "digilocker", "properties": {}, "variables": [] },
{ "id": "module_y", "type": "typeY", "properties": {}, "variables": [] }
]
Workflow Builder
- The stepId field can be found in the properties of the modules.
- If your workflow is an existing workflow as of 21 May 2025, it will not automatically add stepId to the workflow. You can enable it in each of the modules using a toggle.
- If you want to add stepId to all the modules, you can reimport the workflow.
- For Supermodules: stepId auto-filled with snake_case of Supermodule name.
- For Simple Modules: stepId auto-filled with snake_case of Module name.
- You can manually override these values if needed. If a stepId is manually overwritten, it will retain that value and no longer take the value from the name of the module, unless it is toggled on and off.
Event: step_ended
When is it triggered?
- When the last module of a step with a defined stepId completes.
- When transitioning from one step to a new step (stepId change detected).
Sample payload
{
"schemaVersion": "1.0.0",
"eventName": "step_ended",
"timestamp": "2025-04-21T10:35:40.321Z",
"sdkVersion": "0.45.0",
"transactionId": "transactionId_1234",
"workflowId": "onboarding",
"workflowVersion": "1.0.0",
"appId": "abcdef",
"stepId": "digilocker",
"metadata": {}
}
SDK integration: receiving events
Clients can use the SDK-provided addEventListener API to subscribe to real-time milestone events.
Android
Java
// [Recommended] Attach event listeners before launching HyperKYC SDK
HyperKyc.addEventListener(jsonObject -> {
// Handle the step_ended event
// ....
return Unit.INSTANCE;
});
// Remove all event listeners after receiving the SDK response from HyperKYC SDK
HyperKyc.removeAllEventListeners();
Kotlin
// [Recommended] Attach event listeners before launching HyperKYC SDK
HyperKyc.addEventListener { event ->
// Handle the step_ended event
}
// [Recommended] Remove all event listeners after receiving the SDK response from HyperKYC SDK
HyperKyc.removeAllEventListeners()
iOS
// [Recommended] Attach event listeners before launching HyperKYC SDK
HyperKyc.addEventListener { event in
// Handle the step_ended event
}
// [Recommended] Remove all event listeners after receiving the SDK response from HyperKYC SDK
HyperKyc.removeAllEventListeners()
Web
// [Recommended] Attach event listeners before launching HyperKYC SDK
HyperKYCModule.addEventListener((event) => {
// Handle the step_ended event
})
// [Recommended] Remove all event listeners after receiving the SDK response from HyperKYC SDK
HyperKYCModule.removeAllEventListeners()
Flutter
// [Recommended] Attach event listeners before launching HyperKYC SDK
HyperKyc.addEventListener(listener: (event) {
// Handle the step_ended event
});
// [Recommended] Remove all event listeners after receiving the SDK response from HyperKYC SDK
await HyperKyc.removeAllEventListeners();
Advanced use case [Flutter ↔ Android]
You only need to implement the setup below if:
- You're targeting devices where:
- "Don't Keep Activities" is enabled, or
- Low-memory scenarios are likely
Without this setup, addEventListener() and removeAllEventListeners() may stop working when navigating between native and Flutter screens in the above mentioned scenarios (Don't Keep Activities enabled or Low Memory scenarios).
Setup for reliable event handling (advanced use cases)
In MainApplication.java
@Override
public void onCreate() {
super.onCreate();
FlutterEngine flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
);
FlutterEngineCache.getInstance().put("unique_engine_id", flutterEngine);
}
In MainActivity.java
public class MainActivity extends FlutterActivity {
@Override
public FlutterEngine provideFlutterEngine(Context context) {
return FlutterEngineCache.getInstance().get("unique_engine_id");
}
}
Reuse an existing cached engine if you already use one — no need to initialize a new one.
FAQ
| Question | Answer |
|---|---|
| Do I need this setup in all cases? | No. Only if your app uses Flutter + native Android and may run on devices with aggressive memory clearing. |
| What happens without this setup? | addEventListener() and similar callbacks may stop working after FlutterActivity gets destroyed. |
| We already use a cached FlutterEngine. Can we reuse it? | Yes, reuse the same engine name. |
| Is this needed for iOS? | No. iOS retains the Flutter engine across transitions by default. |
Risk assessment
| Touchpoint | Description | Risk | Impact |
|---|---|---|---|
| App Startup Time | Slightly slower due to FlutterEngine init | Low | ~30–80ms |
| Memory Usage | One FlutterEngine stays in memory | Low–Medium | ~5–15MB |
| Plugin Conflicts | Sharing engine with other plugins | Very Low | N/A |
| Dev Effort | Minor changes to MainActivity and MainApplication | Low | ~10–15 lines |
| If Setup is Skipped | Event listeners may stop working in "DKA-enabled" and "Low-Memory" scenarios where Flutter Activity gets destroyed. | High | Event drop-offs in the mentioned edge cases. |
Summary table
| Requirement | Purpose | Optional? | iOS Impact? | Can Be Shared? |
|---|---|---|---|---|
provideFlutterEngine() override | Keeps Flutter alive across native screens | Only if needed | Not needed | Yes |
FlutterEngineCache.put(...) | Globally accessible engine | Only if needed | Not needed | Yes |
Final recommendation
- Recommended: Add the setup only if your app frequently navigates between Flutter and native Android screens and you anticipate low-memory situations or "Don't Keep Activities" to be relevant for your user base.
- If a cached engine already exists, reuse it to reduce duplication.
- No action required for iOS — FlutterEngine detachment isn't an issue there.
React Native
// Import HyperKyc from react-native-hyperkyc-sdk
import HyperKyc from 'react-native-hyperkyc-sdk';
// [Recommended] Attach event listeners before launching HyperKYC SDK
HyperKyc.addEventListener((event) => {
// Handle the step_ended event
});
// [Recommended] Remove all event listeners after receiving the SDK response from HyperKYC SDK
HyperKyc.removeAllEventListeners();
Notes & clarifications
addEventListener()is not the same as DOM event listeners; it simply acts as a callback mechanism.removeAllEventListeners()clears any internal references — must be called at the end of the journey to avoid dangling references.- Calling
removeAllEventListenersinside an event listener does not cancel pending future events (in Android & Web platforms, yet).
Best practices
- Attach event listeners before launching the HyperKYC SDK.
- Do not execute blocking code (e.g., synchronous heavy logic) inside the event listener callback.
- Especially important for JavaScript-based environments that run on a single thread.
- Use async/await, setTimeout, or background queues.
- Do not execute main-thread blocking code inside the event listener callback.
- Always call
removeAllEventListeners()after SDK completion to avoid memory leaks.
Feature support matrix
| Scenario | Web | Android | iOS | Flutter | React Native |
|---|---|---|---|---|---|
| Attach one event listener | Yes | Yes | Yes | Yes | Yes |
| Main thread blocking code in listener (synchronous/asynchronous) | No | No | No | No | No |
| Background thread blocking code in listener (synchronous) | No | Yes | Yes | No | No |
| Background thread non-blocking code in listener (async/deferred) | Yes | Yes | Yes | Yes | Yes |
| Attach multiple listeners | Yes | Yes | Yes | Yes | Yes |
| Remove all event listeners | Yes | Yes | Yes | Yes | Yes |
| Remove individual listener | No | No | No | No | No |
removeAllEventListeners inside an event listener | Future | Future | Yes | Future | Future |
| Event drop-offs post SDK completion (SDK closes before triggering all event listeners) | No drops | Future | No drops | Future | Future |
| App is restarted (e.g., permissions updated through settings app) | No | No | No | No | No |
| Attach event listeners through Worker threads or Services | NA | Future | NA | Future | Future |