Response Events
During the OCR flow, the SDK reports progress and results as the user scans the front and back of a CNIC card. On Android, events are delivered via a broadcast receiver; on iOS, they are delivered through the OcrControllerDelegate methods (see iOS delivery).
The sides captured depend on the scanMode parameter — see Scan mode and results.
On-device text recognition and field parsing run inside the SDK: once the scan completes, the SDK recognizes the text on the front card and parses it into structured fields, returned as extractedData on the final result. This runs for new (chip-based) CNICs only — old cards have no machine-readable text fields, so they always return an empty object. Recognition and parsing never raise errors — if they fail, extractedData is simply an empty object and the flow still completes normally.
Each Android event follows a common envelope structure:
| Key | Type | Description |
|---|---|---|
type | String | Event identifier, read with intent.getStringExtra("type"). One of the values listed in the events table below. |
payload | Bundle | Event-specific data, delivered as the remaining Intent extras (intent.extras). Empty when the event has no payload. |
Events
| Event Identifier | Payload | Description | Behavior After Post |
|---|---|---|---|
LAUNCH_FAILURE | none | The SDK failed to start because the required identity number is missing. | Activity exits. No further events are sent. |
FRONT_SUCCESS | image | The front of the CNIC was captured successfully. | "frontAndBack": flow continues to back-card capture. "frontOnly": text recognition runs, then OCR_SUCCESS is posted. |
BACK_SUCCESS | image | The back of the CNIC was captured successfully. | Loading UI is shown while the barcode on the back card is read. |
OCR_SUCCESS | See OCR_SUCCESS payload | The scan completed and the full result is available. | Activity exits. |
OCR_FAILURE | none | The identity number could not be read from the back-card barcode. | User is prompted to re-scan the back card only. |
FRONT_FAILURE | none | Reserved. Not posted by the current SDK version. | — |
BACK_FAILURE | none | Reserved. Not posted by the current SDK version. | — |
If the user closes the scan screen (close button or system back), the activity exits without posting an event. Treat the activity finishing without an OCR_SUCCESS as a cancellation.
Blurred, shaky or wrong-side frames are not reported as events. The SDK shows guidance on screen and keeps scanning until it captures a valid frame.
Event Payloads
FRONT_SUCCESS Payload
| Key | Type | Description |
|---|---|---|
image | String | Absolute file path of the captured front CNIC image (PNG in the app's private storage), saved at the image width specified at launch. The same path is included again as frontImage in the final OCR_SUCCESS payload. |
BACK_SUCCESS Payload
| Key | Type | Description |
|---|---|---|
image | String | Absolute file path of the captured back CNIC image, saved at the same width as the front image. The same path is included again as backImage in the final OCR_SUCCESS payload. |
OCR_SUCCESS Payload
Returned when the scan completes. This is the final result object for a completed flow. Some keys are absent depending on the scan mode.
| Key | Type | Description |
|---|---|---|
identityNumber | String | The CNIC number read from the back-card barcode. Compare this against the identity number you provided at launch to verify the document matches the expected holder. Absent for "frontOnly" scans. |
frontImage | String | The captured front image: an absolute file path on Android, a Base64-encoded JPEG on iOS. Absent (Android) or empty (iOS) for "backOnly" scans. |
backImage | String | The captured back image: an absolute file path on Android, a Base64-encoded JPEG on iOS. Absent (Android) or empty (iOS) for "frontOnly" scans. |
documentType | "new" | "old" | Detected CNIC format. "new" indicates the chip-based card; "old" indicates the legacy format. Use this to branch UI or validation logic if your app handles both card types differently. |
distance | Int (Android) / Double (iOS) | Result of comparing the barcode CNIC number with the CNIC number provided at launch. The current SDK version does an exact comparison: 0 means the numbers match, 1 means they differ. Absent on Android, and 1 on iOS, for "frontOnly" scans. |
extractedData | Object | Structured fields parsed from the front card by on-device text recognition. See extractedData object. Empty ({}) for old cards, "backOnly" scans, or when nothing could be recognized or parsed — this never produces a failure event. |
extractedData object
The SDK runs on-device text recognition (Apple Vision on iOS, ML Kit on Android) on the captured front image and parses the recognized text into the fields below. This happens for new (chip-based) CNICs only; old cards have no machine-readable text fields and always return an empty object. Every parsed field is optional — only fields that were confidently parsed are present, and a field that cannot be found is omitted entirely. Alongside the parsed fields, the object also carries raw: the full unparsed text exactly as recognized, included whenever any text was recognized (so you can re-parse or audit it even when individual fields are missing). If recognition produces no text for the whole card, extractedData is an empty object {} and the flow still completes normally.
| Key | Type | Description |
|---|---|---|
name | String | Cardholder's name as printed on the front of the card. |
fatherName | String | Father's name as printed on the card. |
identityNumber | String | CNIC number parsed from the printed text (#####-#######-# or 13 digits). May differ from the top-level identityNumber, which is read from the back-card barcode. For "frontOnly" scans this is the only source of the CNIC number. |
dateOfBirth | String | Date of birth, formatted DD.MM.YYYY. |
dateOfIssue | String | Card issue date, formatted DD.MM.YYYY. |
dateOfExpiry | String | Card expiry date, formatted DD.MM.YYYY. |
gender | "M" | "F" | "X" | Cardholder's gender code as printed on the card: M (male), F (female), or X (transgender). |
country | String | Country of stay. |
raw | String | The complete unparsed text recognized on the front card, exactly as returned by on-device text recognition (may contain line breaks). Present whenever any text was recognized — even if none of the structured fields above could be parsed. Absent only for old cards, "backOnly" scans, or when recognition produced no text. |
On Android, extractedData is delivered as a nested Bundle under the extractedData key of the OCR_SUCCESS extras — read it with payload.getBundle("extractedData"). On iOS it is delivered as a native ExtractedData value on the handleResponse delegate callback.
Scan Mode and Results
| Scan mode | Events posted (Android) | OCR_SUCCESS contents |
|---|---|---|
"frontAndBack" | FRONT_SUCCESS, BACK_SUCCESS, OCR_SUCCESS (OCR_FAILURE on barcode re-scan) | All keys. |
"frontOnly" | FRONT_SUCCESS, OCR_SUCCESS | No identityNumber, backImage or distance (iOS: identityNumber is nil, backImage is empty, distance is 1). Use extractedData.identityNumber for the CNIC number. |
"backOnly" | BACK_SUCCESS, OCR_SUCCESS (OCR_FAILURE on barcode re-scan) | No frontImage (iOS: empty). extractedData is empty. |
iOS Delivery
iOS does not post the per-side events. The OcrControllerDelegate methods map to the flow as follows:
| Delegate method | When it is called |
|---|---|
handleResponse(...) | The scan completed. Arguments match the OCR_SUCCESS payload; identityNumber is String? and is nil for "frontOnly" scans. |
handleError(code:) | The camera is unavailable or could not be configured. The current SDK version sends the code no_camera. |
handleInfo(message:) | Informational messages: "Ocr initialized" when the controller is created, and "Ocr closed" when the user taps the close button. |
When a captured frame fails validation or the back-card barcode cannot be read, the iOS SDK prompts the user to scan again without calling a delegate method.
Example Responses
FRONT_SUCCESS
{
"type": "FRONT_SUCCESS",
"payload": {
"image": "/data/user/0/<app-id>/app_identity_images/front_<uuid>.png"
}
}
BACK_SUCCESS
{
"type": "BACK_SUCCESS",
"payload": {
"image": "/data/user/0/<app-id>/app_identity_images/back_<uuid>.png"
}
}
OCR_SUCCESS
{
"type": "OCR_SUCCESS",
"payload": {
"identityNumber": "12345-1234567-1",
"frontImage": "/data/user/0/<app-id>/app_identity_images/front_<uuid>.png",
"backImage": "/data/user/0/<app-id>/app_identity_images/back_<uuid>.png",
"documentType": "new",
"distance": 0,
"extractedData": {
"name": "John Doe",
"fatherName": "Richard Doe",
"identityNumber": "12345-1234567-1",
"dateOfBirth": "01.01.1990",
"dateOfIssue": "01.01.2020",
"dateOfExpiry": "01.01.2030",
"gender": "M",
"country": "Pakistan",
"raw": "Pakistan\nNational Identity Card\nName\nJohn Doe\nFather Name\nRichard Doe\nGender\nM\nCountry of Stay\nPakistan\nIdentity Number\n12345-1234567-1\nDate of Birth\n01.01.1990\nDate of Issue\n01.01.2020\nDate of Expiry\n01.01.2030"
}
}
}
For old cards, or when recognition or parsing yields nothing, extractedData is an empty object:
{
"type": "OCR_SUCCESS",
"payload": {
"identityNumber": "12345-1234567-1",
"frontImage": "/data/user/0/<app-id>/app_identity_images/front_<uuid>.png",
"backImage": "/data/user/0/<app-id>/app_identity_images/back_<uuid>.png",
"documentType": "old",
"distance": 0,
"extractedData": {}
}
}
For a "frontOnly" scan, the barcode fields are absent:
{
"type": "OCR_SUCCESS",
"payload": {
"frontImage": "/data/user/0/<app-id>/app_identity_images/front_<uuid>.png",
"documentType": "new",
"extractedData": {
"identityNumber": "12345-1234567-1",
"name": "John Doe"
}
}
}
Events With No Payload
LAUNCH_FAILURE and OCR_FAILURE are posted with a type only and no payload:
{
"type": "OCR_FAILURE"
}