Skip to main content

Using callbacks

BlinkID emits data for different events while it scans identity documents. You can grab that data using one of the following callback functions:

All of these callbacks are available from the general @microblink/blinkid package, as well as from these individual components: @microblink/blinkid-ux-manager, @microblink/camera-manager.

How to get results of a scan?

To get scan results, use addOnResultCallback().

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

// after scanning, show results in the console
blinkid.addOnResultCallback((result) => {
console.log("Result:", result);

When a scanning session is completed, addOnResultCallback() will pass the result to a function you provide as a parameter. In the example above, we're logging the result to the console.

How to get results of an uncompleted scan?

You can also grab the results of an uncompleted scan by calling the getResult method directly. For this, you need to use the blinkid-core package.

import { loadBlinkIdCore } from "@microblink/blinkid-core";

const blinkIdCore = await loadBlinkIdCore({
// ...initialize, configure

// Create a scanning session:
const session = await blinkIdCore.createBlinkIdScanningSession();

// Process a frame. In this example we're processing an ImageData object, but you
// might, for example, add frames from a video stream instead.
const frameResult = await session.process(new ImageData(1920, 1080));

// Show scan results as they become available. The scan doesn't have to be complete.
const result = await session.getResult();
console.log("result", result);

How to filter specific documents?

To filter out (or in) specific document types, use addDocumentClassFilter() together with the addOnDocumentFilteredCallback()

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

blinkid.addDocumentClassFilter((documentClassInfo) => {
return documentClassInfo.type === "id";
});

blinkid.addOnDocumentFilteredCallback(() => {
console.log("The document is not an ID card.");
});

In the example above, we are filtering in only ID cards. First we add a document class filter that says "return true if the presented document is an ID card". Then, when a scan occurs, we check if we received a return of true, and if so, we can proceed; otherwise, we log to the console the notice that the document isn't an ID card.

We could also filter documents out, i.e. specifically disallow certain kinds of documents. Let's say we don't want to allow driver licenses:

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

blinkid.addDocumentClassFilter((documentClassInfo) => {
return documentClassInfo.type !== "dl";
});

blinkid.addOnDocumentFilteredCallback(() => {
console.log("The document is a driver license, and driver licenses aren't supported.");
});

Here we are saying "return true if the presented document isn't a driver license". But when we present a driver license for scanning, the filter doesn't return a true value, and a filtering callback is activated.

You can also combine multiple document features, such as only allowing certain types of documents from certain countries, or disallowing specific regions. See this example app, in which every document except a US passport is not allowed.

The following is a list of documentClassInfo fields that you can use (and combine, and chain) for filtering:

  • country
  • countryName
  • isoAlpha2CountryCode
  • isoAlpha3CountryCode
  • isoNumericCountryCode
  • region
  • type

See the reference for information about subtypes and allowed values.

How to get frame processing results for an uncompleted scan?

To get frame processing results for an uncompleted (ongoing) scan, use addOnFrameProcessCallback().

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

blinkid.blinkIdUxManager.addOnFrameProcessCallback((frameResult) => {
console.log('Frame processed:', frameResult);
});

In the example above, whenever a there's a frame change, the console logs the entire result (or the absence of it):

Click to open
{
"inputImageAnalysisResult": {
"processingStatus": "detection-failed",
"missingMandatoryFields": [],
"extractedFields": [],
"invalidCharacterFields": [],
"extraPresentFields": [],
"imageExtractionFailures": [],
"scanningSide": "first",
"documentDetectionStatus": "failed",
"documentClassInfo": {
"countryName": "",
"isoNumericCountryCode": "",
"isoAlpha2CountryCode": "",
"isoAlpha3CountryCode": ""
},
"blurDetectionStatus": "not-available",
"glareDetectionStatus": "not-available",
"documentColorStatus": "not-available",
"documentMoireStatus": "not-available",
"faceDetectionStatus": "not-available",
"mrzDetectionStatus": "not-available",
"barcodeDetectionStatus": "not-available",
"realIDDetectionStatus": "not-available",
"documentLightingStatus": "not-available",
"documentHandOcclusionStatus": "not-available",
"documentOrientation": "not-available",
"documentRotation": "not-available"
},
"resultCompleteness": {
"scanningStatus": "scanning-side-in-progress",
"vizExtracted": false,
"mrzExtracted": false,
"barcodeExtracted": false,
"documentImageExtracted": false,
"faceImageExtracted": false,
"signatureImageExtracted": false
},
"arrayBuffer": {}
}

Note that performance-wise, it's not a good idea to append the output of addOnFrameProcessCallback() to a data structure without any filtering because, as the name implies, it generates data on every frame, so you might quickly exhaust your RAM.

If you're looking for a specific field in the scan result, and you don't want to rely on one of the builtin callbacks from the intro, you should wrap your saving logic in a conditional statement that checks if the field you're interested in is present, for example:

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

let capturedData = null;
let hasBeenCaptured = false;
const TARGET_FIELD = "some value you're interested in";

const savedFrameData = blinkid.blinkIdUxManager.addOnFrameProcessCallback((frameResult) => {
if (!hasBeenCaptured) {
// we're using an non-existent path in this example
const nestedValue = frameProcessResult.inputImageAnalysisResult.some.field.that.interests.you
if (nestedValue === TARGET_VALUE) {
console.log("Success!");
capturedData = frameProcessResult;
hasBeenCaptured = true;
}
}
}

How to catch error messages for failing scans?

To catch general error messages, use addOnErrorCallback().

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

blinkid.addOnErrorCallback((error) => {
if (error === "timeout") {
console.log("Timeout!")
} else if (error === "unknown") {
console.log("Unknown!);
}

Possible callback values are timeout and unknown.

This callback can be used in combination with the addOnFrameProcessedCallback(), for example by saving a subset of its output and then reading that output in case the error callback is activated, and deleting the output in case no error is detected. This way you get detailed diagnostic information for each processed frame, but only if an error occurred.

In this example, we just continuously overwrite the lastCapturedFrame object until such time that there's an error, and then we present the "diagnostic" frame data in case of an error:

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

let lastCapturedFrame = null;
const frameData = blinkid.blinkIdUxManager.addOnFrameProcessCallback(
(frameResult) => {
lastCapturedFrame = frameResult;
}
);

blinkid.addOnErrorCallback((error) => {
console.log(`An error occurred: ${error}.`);
console.log(`This is the last captured frame:`, lastCapturedFrame);
});

How to respond to UI and camera changes?

There are numerous "cues" that BlinkID provides to the user with regards to how to present a document. Mostly these are things like "move the document closer" or "make sure that the document is fully readable": instructions for the user to change positioning or lighting. This also includes instructions to flip the document, if it's a two-sided kind. All of these instructions happen by themselves and require no intervention or manual integration, but you may want to add your own logic that responds to such changes.

In that case, you can use addOnUiStateChangedCallback():

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

blinkid.blinkIdUxManager.addOnUiStateChangedCallback((uiState) => {
if (uiState.key === "DOCUMENT_FRAMING_CAMERA_TOO_CLOSE") {
console.log("The document is too close to the camera.");
}
});

The example above is redundant: the message logged to the console is presented to the user anyway, but instead of logging the message, you might perform some custom logic.

The same goes for all other key values for the UI state. The full list is available in the API reference.

A special case of UI changes is when the camera UI component is removed from the page. This happens after a scan is completed, and you might want to add special logic for each camera dismount. In that case, you can use addOnDismountCallback()[/sdk/web/api-ref/blinkid/type-aliases/CameraManagerComponent/#addondismountcallback):

import { createBlinkId } from "@microblink/blinkid";

const blinkid = await createBlinkId({
// ...initialize, configure, scan

blinkid.addOnResultCallback((result) => {
console.log("Result caught");
void blinkid.destroy();
});

blinkid.cameraUi.addOnDismountCallback(() => {
console.log("Camera UI dismounted");
});

In the example above, after we call the destroy() method, the camera UI component gets dismounted, and the dismount callback is activated. Again, we're simply logging a dismount message to the console, but if there's any custom logic to implement for the dismount, this is the place.