diff --git a/CHANGELOG.md b/CHANGELOG.md
index 762389d..b5f3953 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## [2.0.0] - 10-06-2024
+
+-   (feat): `init` is now initialised with config and callbacks to retrieve image blob
+-   (feat): add `onComplete` and `onPreProcessingChecksFailed` callbacks
+-   (refactor): `init` no longer returns a promise
+-   (refactor): sdk no longer returns image `metadata`
+
 ## [1.5.1] - 11-06-2024
 
 -   (chore): load demo gif asynchronously before init is called
diff --git a/README.md b/README.md
index f64cad7..859cf4f 100644
--- a/README.md
+++ b/README.md
@@ -2,74 +2,109 @@
 
 Anyline guidance sdk to retrieve high resolution image from a video stream with tire overlay that helps to point accurately to the tire.
 
+See [Migrating from v1 to v2](#migrating-from-v1-to-v2)
+
 ## Installation
 
 ```shell
 npm install @anyline/anyline-guidance-sdk
 ```
 
-The next step varies based on which environment you are working on.
-
-### ESM
+### SDK Config and Callbacks
 
 ```js
 import init from '@anyline/anyline-guidance-sdk';
+// ...
+// call init when you want to start the sdk
+init(config, callbacks);
 ```
 
-Call the `init()` (refer [Code Example](#code-example)) function whenever you'd want the sdk to start.
+#### 1. `config` (**required**)
 
-### Direction script tag inclusions
-
-Refer [index.html](./public/index.html) for demo implementation.
+The `config` value is used to apply developer specific settings to the SDK.
+Currently, `config` can be used to control the number of times onboarding instructions are shown.
+For example:
 
 ```js
-<script src="anyline-guidance-sdk.js"></script>
+const config = { 
+  onboardingInstructions: { 
+    timesShown: 3, 
+  },
+};
 ```
 
-You can get `anyline-guidance-sdk.js` files in the following ways:
-
-By [installing the package](https://github.com/Anyline/anyline-guidance-sdk) and copying `index.js` from `dist/iife/` folder into your project.
+In this example, onboarding instructions will be shown for a total of 3 times, after which the onboarding instructions will be skipped and sdk will start directly at the Video Stream screen.
 
-(OR)
+> **_NOTE:_** `timesShown` is stored in the localStorage. Clearing the localStorage will reset the setting.
 
-You can also build the sdk by yourself and copy `index.js` from `dist/iife` folder (refer [To Build](#to-build) section).
-
-[//]: # "Call `Anyline.default()` function from within your code whenever you'd want the sdk to start."
-
-### Code Example
+If you wish to show the onboarding instructions everytime the sdk is initialised, set `config` like so
 
 ```js
-const { blob, metadata } = await init();
-// blob represents the image captured by SDK in Blob format
-blob: Blob
-// other metadata related to image viz. width, height and fileSize
-metadata: {
-  width: number,
-  height: number,
-  fileSize: number,
-}
+const config = {};
 ```
 
-### SDK Config
+#### 2. `callbacks` (**required**)
 
-By default, we show onboarding instructions screen everytime the SDK opens, this informs users how to capture a better tire image. You can configure this setting to limit the number of times this onboarding instructions screen is shown.
+The `callbacks` object consists of two functions: `onComplete` and `onPreProcessingChecksFailed`
 
-To achieve this, call sdk `init` method like so:
+-   `onComplete` (**required**) is called once the SDK has finished processing the image.
+-   `onPreProcessingChecksFailed` (**optional**) is called when the image captured by an end-user has failed to pass image quality checks. The user has the option to either proceed with the image or take a new picture.
+    Example:
 
 ```js
-const { blob } = init({
+const callbacks = { 
+  onComplete: ({ blob }) => {
+    // final returned image
+  }, 
+  onPreProcessingChecksFailed: ({ blob, message }) => {
+    // intermediate image
+  },
+};
+```
+
+---
+
+### Migrating from v1 to v2
+
+#### Key changes
+1. Initialisation
+  - v1: `init` called with a single `config` object and returned a promise
+  - v2: `init` called with two arguments: `config` and `callbacks` and no longer returns a promise (use `callbacks` to retrieve blob).
+2. Config
+  - v1: can be empty
+  - v2: for empty config use `{}`
+3. Callbacks (v2 only)
+  - `onComplete` **required**
+  - `onPreProcessingChecksFailed` **optional**
+
+#### Example
+v1:
+```ts
+const { blob } = await init({
   onboardingInstructions: {
     timesShown: 3
   }
 })
 ```
-where `3` is the number of times you'd want to show the onboarding instructions screen everytime a user opens the SDK. When they open the SDK for the 4th time, they will not see the onboarding instructions screen and will be taken directly to the video stream screen.
 
-This feature comes in handy when we assume that after the user has seen onboarding instruction for a certain number of times, they understand the instructions already and thus do not need to read it again. 
+v2:
+```ts
+const config = {
+  onboardingInstructions: {
+    timesShown: 3
+  }
+};
+const callbacks = {
+  onComplete: ({ blob }) => {
+    // ...
+  }
+}
+init(config, callbacks)
+```
 
-If you do not want to show the instructions at all, you can set `timesShown` to `0`.
+See [SDK Config and Callbacks](#sdk-config-and-callbacks) for detailed implementation about `config` and `callbacks`.
 
-Note: This config works by storing a variable in `localStorage`. If you have a functionality within your website/web app that clears the `localStorage`, then this configuration will not be enforced and the onboarding instructions will be shown everytime regardless of what you set `timesShown` to.
+---
 
 ## Developers / Contributors
 
diff --git a/esbuild/injectCSSPlugin.js b/esbuild/injectCSSPlugin.js
index 08ef4a5..8883a41 100644
--- a/esbuild/injectCSSPlugin.js
+++ b/esbuild/injectCSSPlugin.js
@@ -50,7 +50,7 @@ export const injectCSSPlugin = baseDir => {
 						return;
 					}
 					const regex =
-						/(\w+)\.id="anyline-guidance-sdk-style",\1\.textContent=""/;
+						/([$\w]+)\.id="anyline-guidance-sdk-style",\1\.textContent=""/;
 					const replacement = `$1.id="anyline-guidance-sdk-style",$1.textContent=\`${allCSS}\``;
 					const updatedData = data.replace(regex, replacement);
 					fs.writeFile(filePath, updatedData, 'utf8', err => {
diff --git a/package.json b/package.json
index b1293a6..5da9dd8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"author": "Anyline",
 	"name": "@anyline/anyline-guidance-sdk",
-	"version": "1.5.1",
+	"version": "2.0.0",
 	"main": "dist/cjs/index.js",
 	"module": "dist/esm/index.js",
 	"types": "dist/esm/index.d.ts",
@@ -72,7 +72,9 @@
 		"typescript-eslint": "^7.5.0",
 		"wdio-wait-for": "^3.0.11"
 	},
-	"dependencies": {},
+	"dependencies": {
+		"joi": "^17.13.1"
+	},
 	"resolutions": {
 		"@typescript-eslint/parser": "6.21.0",
 		"@typescript-eslint/eslint-plugin": "6.21.0"
diff --git a/public/index.html b/public/index.html
index cfdc92e..cdd27db 100644
--- a/public/index.html
+++ b/public/index.html
@@ -14,6 +14,11 @@
 				min-width: 100px;
 				height: 54px;
 			}
+			#poor-quality-image-wrapper > img {
+				margin-bottom: 12px;
+				width: 100%;
+				max-width: 500px;
+			}
 		</style>
 		<!-- Anyline Guidance SDK js -->
 		<script src="./build/index.js"></script>
@@ -24,33 +29,45 @@
 					const errorElement = document.getElementById('error');
 					errorElement.innerText = '';
 					try {
-						const {
-							blob,
-							metadata: { width, height, fileSize },
-						} = await Anyline.default(config);
-						blobUrl = URL.createObjectURL(blob);
-						const imgElement = document.getElementById('new-image');
-
-						imgElement.src = blobUrl;
+						Anyline.default(config, {
+							onComplete: ({ blob }) => {
+								blobUrl = URL.createObjectURL(blob);
+								const imgElement =
+									document.getElementById('new-image');
 
-						const specsElement =
-							document.getElementById('image-specs');
-						specsElement.innerText = `Image specs: ${width}px X ${height}px, ${fileSize}kb`;
+								imgElement.src = blobUrl;
+							},
+							onPreProcessingChecksFailed: ({
+								blob,
+								message,
+							}) => {
+								// console.log('Poor quality image', blob);
+								// console.log('Message', message);
+								const poorQualityImageWrapper =
+									document.getElementById(
+										'poor-quality-image-wrapper'
+									);
+								blobUrl = URL.createObjectURL(blob);
+								const imgElement = new Image();
+								imgElement.src = blobUrl;
+								poorQualityImageWrapper.appendChild(imgElement);
+							},
+						});
 					} catch (err) {
 						console.log('Error', err);
 						errorElement.innerText = err;
 					}
 				};
 
+				const config = {
+					onboardingInstructions: {
+						timesShown: 3,
+					},
+				};
+
 				document
 					.getElementById('start-sdk-btn')
-					.addEventListener('click', () =>
-						startSdk({
-							onboardingInstructions: {
-								timesShown: 3,
-							},
-						})
-					);
+					.addEventListener('click', () => startSdk(config, 'asd'));
 
 				document
 					.getElementById('download-button')
@@ -78,17 +95,22 @@
 		<button id="reset-times-shown" class="button">
 			Reset timesShown to 3
 		</button>
-		<br /><br />
+		<br />
+		<hr />
+		Default image section <br />
 		<img
 			id="new-image"
-			alt="Demo"
+			alt=""
 			width="100%"
 			style="max-width: 500px"
 		/><br />
-		<div id="image-specs"></div>
 		<br />
 		<button id="download-button" class="button">Download Image</button>
 		<br />
+		<hr />
+		Poor quality image(s) section <br />
+		<div id="poor-quality-image-wrapper"></div>
+		<br />
 		<div id="error"></div>
 	</body>
 </html>
diff --git a/src/camera/getImageSpecification.ts b/src/camera/getImageSpecification.ts
deleted file mode 100644
index 6149af9..0000000
--- a/src/camera/getImageSpecification.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { type ImageMetadata } from './init';
-
-export async function getImageSpecification(
-	blob: Blob
-): Promise<ImageMetadata> {
-	const img = new Image();
-	const blobUrl = URL.createObjectURL(blob);
-	img.src = blobUrl;
-
-	return await new Promise((resolve, reject) => {
-		img.addEventListener('load', () => {
-			try {
-				const metadata: ImageMetadata = {
-					width: img.width,
-					height: img.height,
-					fileSize: blob.size / 1000,
-				};
-				resolve(metadata);
-			} catch (err) {
-				reject(err);
-			}
-		});
-	});
-}
diff --git a/src/camera/init.ts b/src/camera/init.ts
index d1ddd0b..cea81a5 100644
--- a/src/camera/init.ts
+++ b/src/camera/init.ts
@@ -1,12 +1,11 @@
 import createModal from '../components/modal';
-import { getImageSpecification } from './getImageSpecification';
 import injectCSS from '../lib/injectCSS';
-import ImageManager from '../modules/ImageManager';
 import HostManager from '../modules/HostManager';
 import initRouter from '../lib/initRouter';
-import { type Config } from '../modules/ConfigManager';
+import { type Config } from '../modules/ConfigManager/ConfigManager';
 import OpenCVManager from '../modules/OpenCVManager';
 import DocumentScrollController from '../modules/DocumentScrollController';
+import CallbackHandler from '../modules/CallbackHandler';
 
 // load demoInstructionsImage chunk before it is requested
 // to ensure lower loading time for the gif when sdk is initialised
@@ -16,23 +15,41 @@ void import(
 	console.log('Error loading demo gif');
 });
 
-export interface ImageMetadata {
-	width: number;
-	height: number;
-	fileSize: number;
+export interface OnComplete {
+	blob: Blob;
 }
 
-export interface SDKReturnType {
+export interface OnPreProcessingChecksFailed {
 	blob: Blob;
-	metadata: ImageMetadata;
+	message: string;
+}
+
+export interface Callbacks {
+	onComplete: (response: OnComplete) => void;
+	onPreProcessingChecksFailed?: (
+		response: OnPreProcessingChecksFailed
+	) => void;
 }
 
-async function init(config?: Config): Promise<SDKReturnType> {
+function init(config: Config, callbacks: Callbacks): void {
 	if (
 		navigator.mediaDevices === null ||
 		navigator.mediaDevices === undefined
 	) {
-		await Promise.reject(new Error('Unsupported device'));
+		throw new Error('Unsupported device');
+	}
+
+	const callbackHandler = CallbackHandler.getInstance();
+	// register callbacks
+	const { onComplete, onPreProcessingChecksFailed } = callbacks;
+	callbackHandler.setOnComplete(onComplete);
+	if (
+		onPreProcessingChecksFailed != null &&
+		onPreProcessingChecksFailed !== undefined
+	) {
+		callbackHandler.setOnPreProcessingChecksFailedCallback(
+			onPreProcessingChecksFailed
+		);
 	}
 
 	const opencvManager = OpenCVManager.getInstance();
@@ -51,12 +68,6 @@ async function init(config?: Config): Promise<SDKReturnType> {
 	const modal = createModal(shadowRoot);
 
 	initRouter(modal, config);
-
-	const imageManager = ImageManager.getInstance();
-	const blob = await imageManager.getImageBlob();
-	const metadata = await getImageSpecification(blob);
-
-	return { blob, metadata };
 }
 
 export default init;
diff --git a/src/lib/closeSDK.ts b/src/lib/closeSDK.ts
index b6ba67a..195a6de 100644
--- a/src/lib/closeSDK.ts
+++ b/src/lib/closeSDK.ts
@@ -1,4 +1,5 @@
-import ConfigManager from '../modules/ConfigManager';
+import CallbackHandler from '../modules/CallbackHandler';
+import ConfigManager from '../modules/ConfigManager/ConfigManager';
 import DocumentScrollController from '../modules/DocumentScrollController';
 import HostManager from '../modules/HostManager';
 import ImageChecker from '../modules/ImageChecker';
@@ -7,6 +8,7 @@ import LocalStorageManager from '../modules/LocalStorageManager';
 import Router from '../modules/Router';
 
 export default function closeSDK(): void {
+	const callbackHandler = CallbackHandler.getInstance();
 	const configManager = ConfigManager.getInstance();
 	const documentScrollController = DocumentScrollController.getInstance();
 	const hostManager = HostManager.getInstance();
@@ -15,6 +17,7 @@ export default function closeSDK(): void {
 	const localStorageManager = LocalStorageManager.getInstance();
 	const routerManager = Router.getInstance();
 
+	callbackHandler.destroy();
 	configManager.destroy();
 	documentScrollController.enableScroll();
 	documentScrollController.destroy();
diff --git a/src/lib/initRouter.ts b/src/lib/initRouter.ts
index d3a0cd5..a2468a5 100644
--- a/src/lib/initRouter.ts
+++ b/src/lib/initRouter.ts
@@ -1,4 +1,6 @@
-import ConfigManager, { type Config } from '../modules/ConfigManager';
+import ConfigManager, {
+	type Config,
+} from '../modules/ConfigManager/ConfigManager';
 import LocalStorageManager from '../modules/LocalStorageManager';
 import Router from '../modules/Router';
 import OnboardingScreen from '../screens/onboardingInstructions';
@@ -7,7 +9,7 @@ import closeSDK from './closeSDK';
 
 export default function initRouter(
 	modal: HTMLDivElement,
-	config?: Config
+	config: Config
 ): { onboardingInstructionsShown: boolean } {
 	// mount modal
 	const router = Router.getInstance();
@@ -16,18 +18,20 @@ export default function initRouter(
 	let configManager: ConfigManager;
 
 	try {
-		configManager = ConfigManager.getInstance(config);
+		configManager = ConfigManager.getInstance();
+		configManager.setConfig(config);
 	} catch (err) {
 		closeSDK();
 		throw err;
 	}
 
-	const timesShown =
-		configManager.getConfig()?.onboardingInstructions?.timesShown;
+	const _config = configManager.getConfig();
 
-	let shouldShowOnboarding: boolean;
+	const timesShown = _config?.onboardingInstructions?.timesShown ?? null;
 
-	if (timesShown != null) {
+	let shouldShowOnboarding = true;
+
+	if (timesShown !== null) {
 		const localStorageManager = LocalStorageManager.getInstance();
 
 		localStorageManager.setTimesOnboardingInstructionsShown(timesShown);
diff --git a/src/lib/preProcessImage.ts b/src/lib/preProcessImage.ts
index 05a8f61..15513be 100644
--- a/src/lib/preProcessImage.ts
+++ b/src/lib/preProcessImage.ts
@@ -4,16 +4,16 @@ const CONTRAST_THRESHOLD = 30;
 const CANNY_LOWER_THRESHOLD = 50;
 const CANNY_UPPER_THRESHOLD = 300;
 
-const undefinedMetrics = {
-	isBlurDetected: undefined,
-	isEdgeDetected: undefined,
-	isContrastLow: undefined,
+const defaultMetrics = {
+	isBlurDetected: false,
+	isEdgeDetected: true,
+	isContrastLow: false,
 };
 
 export default async function preProcessImage(blob: Blob): Promise<{
-	isBlurDetected?: boolean;
-	isEdgeDetected?: boolean;
-	isContrastLow?: boolean;
+	isBlurDetected: boolean;
+	isEdgeDetected: boolean;
+	isContrastLow: boolean;
 }> {
 	return await new Promise((resolve, reject) => {
 		const img = new Image();
@@ -26,7 +26,7 @@ export default async function preProcessImage(blob: Blob): Promise<{
 				const canvas = document.createElement('canvas');
 				const ctx = canvas.getContext('2d');
 				if (ctx == null) {
-					resolve(undefinedMetrics);
+					resolve(defaultMetrics);
 					return;
 				}
 				const fixedWidth = 800;
@@ -50,9 +50,9 @@ export default async function preProcessImage(blob: Blob): Promise<{
 					}
 				}
 
-				let isBlurDetected;
-				let isEdgeDetected;
-				let isContrastLow;
+				let { isBlurDetected, isEdgeDetected, isContrastLow } =
+					defaultMetrics;
+
 				if (cv !== undefined) {
 					const src = cv.matFromImageData(imgData);
 					const dst = new cv.Mat();
diff --git a/src/modules/CallbackHandler.ts b/src/modules/CallbackHandler.ts
new file mode 100644
index 0000000..abb9a39
--- /dev/null
+++ b/src/modules/CallbackHandler.ts
@@ -0,0 +1,56 @@
+import {
+	type OnPreProcessingChecksFailed,
+	type OnComplete,
+} from '../camera/init';
+
+export default class CallbackHandler {
+	private static instance: CallbackHandler | null = null;
+
+	private onComplete: ((response: OnComplete) => void) | null = null;
+	private onPreProcessingChecksFailedCallback:
+		| ((response: OnPreProcessingChecksFailed) => void)
+		| null = null;
+
+	private constructor() {}
+
+	public static getInstance(): CallbackHandler {
+		if (CallbackHandler.instance === null) {
+			CallbackHandler.instance = new CallbackHandler();
+		}
+		return CallbackHandler.instance;
+	}
+
+	public setOnComplete(callback: (response: OnComplete) => void): void {
+		this.onComplete = callback;
+	}
+
+	public callOnComplete(response: OnComplete): void {
+		if (this.onComplete != null) {
+			this.onComplete(response);
+		} else {
+			console.error('onComplete callback function is not set.');
+		}
+	}
+
+	public setOnPreProcessingChecksFailedCallback(
+		callback: (response: OnPreProcessingChecksFailed) => void
+	): void {
+		this.onPreProcessingChecksFailedCallback = callback;
+	}
+
+	public callOnPreProcessingChecksFailed(
+		response: OnPreProcessingChecksFailed
+	): void {
+		if (this.onPreProcessingChecksFailedCallback != null) {
+			this.onPreProcessingChecksFailedCallback(response);
+			return;
+		}
+		throw new Error('pre-processing callback not set');
+	}
+
+	public destroy(): void {
+		this.onComplete = null;
+		this.onPreProcessingChecksFailedCallback = null;
+		CallbackHandler.instance = null;
+	}
+}
diff --git a/src/modules/ConfigManager.ts b/src/modules/ConfigManager.ts
deleted file mode 100644
index e48e270..0000000
--- a/src/modules/ConfigManager.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-export interface Config {
-	onboardingInstructions: {
-		timesShown: number;
-	};
-}
-
-export default class ConfigManager {
-	private static instance: ConfigManager | null = null;
-	private readonly config: Config | null = null;
-
-	private constructor(config?: Config) {
-		if (config !== undefined) {
-			// use external library for schema validation if config increases
-			if (typeof config !== 'object')
-				throw new Error('Invalid config format');
-			if (
-				!Object.prototype.hasOwnProperty.call(
-					config,
-					'onboardingInstructions'
-				)
-			)
-				throw new Error('Config must have onboardingInstructions set');
-			if (typeof config.onboardingInstructions !== 'object')
-				throw new Error('onboardingInstructions must be an object');
-			if (typeof config.onboardingInstructions.timesShown !== 'number')
-				throw new Error('timesShown should be a number');
-			if (config.onboardingInstructions.timesShown < 0)
-				throw new Error(
-					'timesShown should be greater than or equal to 0'
-				);
-			this.config = config;
-		}
-	}
-
-	public static getInstance(config?: Config): ConfigManager {
-		if (ConfigManager.instance === null) {
-			ConfigManager.instance = new ConfigManager(config);
-		}
-		return ConfigManager.instance;
-	}
-
-	public getConfig(): Config | null {
-		return this.config;
-	}
-
-	public destroy(): void {
-		ConfigManager.instance = null;
-	}
-}
diff --git a/src/modules/ConfigManager/ConfigManager.ts b/src/modules/ConfigManager/ConfigManager.ts
new file mode 100644
index 0000000..692f6ce
--- /dev/null
+++ b/src/modules/ConfigManager/ConfigManager.ts
@@ -0,0 +1,32 @@
+import validateSchema from './validateSchema';
+
+export interface Config {
+	onboardingInstructions?: {
+		timesShown: number;
+	};
+}
+
+export default class ConfigManager {
+	private static instance: ConfigManager | null = null;
+	private config: Config | null = null;
+
+	public static getInstance(): ConfigManager {
+		if (ConfigManager.instance === null) {
+			ConfigManager.instance = new ConfigManager();
+		}
+		return ConfigManager.instance;
+	}
+
+	public setConfig(config: Config): void {
+		const value = validateSchema(config);
+		this.config = value;
+	}
+
+	public getConfig(): Config | null {
+		return this.config;
+	}
+
+	public destroy(): void {
+		ConfigManager.instance = null;
+	}
+}
diff --git a/src/modules/ConfigManager/schema.ts b/src/modules/ConfigManager/schema.ts
new file mode 100644
index 0000000..a0da254
--- /dev/null
+++ b/src/modules/ConfigManager/schema.ts
@@ -0,0 +1,11 @@
+import Joi from 'joi';
+
+const onboardingInstructionsSchema = Joi.object({
+	timesShown: Joi.number().integer().min(0).required(),
+}).optional();
+
+const schema = Joi.object({
+	onboardingInstructions: onboardingInstructionsSchema,
+}).required();
+
+export default schema;
diff --git a/src/modules/ConfigManager/validateSchema.ts b/src/modules/ConfigManager/validateSchema.ts
new file mode 100644
index 0000000..b82d466
--- /dev/null
+++ b/src/modules/ConfigManager/validateSchema.ts
@@ -0,0 +1,10 @@
+import { type Config } from './ConfigManager';
+import schema from './schema';
+
+export default function validateSchema(config: Config): Config {
+	const { error, value } = schema.validate(config);
+
+	if (error != null) throw error;
+
+	return value;
+}
diff --git a/src/modules/ImageChecker.ts b/src/modules/ImageChecker.ts
index 73d8eb3..adac190 100644
--- a/src/modules/ImageChecker.ts
+++ b/src/modules/ImageChecker.ts
@@ -2,9 +2,15 @@ import preProcessImage from '../lib/preProcessImage';
 
 export default class ImageChecker {
 	private static instance: ImageChecker | null = null;
-	public blob: Blob | null = null;
+	private blob: Blob | null = null;
+	private readonly blobSetPromise: Promise<Blob>;
+	private blobSetResolve: ((blob: Blob) => void) | null = null;
 
-	private constructor() {}
+	private constructor() {
+		this.blobSetPromise = new Promise<Blob>(resolve => {
+			this.blobSetResolve = resolve;
+		});
+	}
 
 	public static getInstance(): ImageChecker {
 		if (ImageChecker.instance === null) {
@@ -14,12 +20,18 @@ export default class ImageChecker {
 	}
 
 	public setImageBlob(blob: Blob): void {
-		this.blob = blob;
+		if (this.blob == null) {
+			this.blob = blob;
+			if (this.blobSetResolve != null) {
+				this.blobSetResolve(this.blob);
+			}
+		}
 	}
 
-	public getImageBlob(): Blob {
-		if (this.blob === null) throw new Error('No image found');
-		return this.blob;
+	public onBlobSet(callback: (blob: Blob) => Promise<void>): void {
+		void this.blobSetPromise.then(async () => {
+			if (this.blob !== null) await callback(this.blob);
+		});
 	}
 
 	public async isImageQualityGood(): Promise<boolean> {
@@ -27,11 +39,7 @@ export default class ImageChecker {
 		try {
 			const { isBlurDetected, isContrastLow, isEdgeDetected } =
 				await preProcessImage(this.blob);
-			return (
-				!(isBlurDetected ?? false) &&
-				(isEdgeDetected ?? false) &&
-				!(isContrastLow ?? false)
-			);
+			return !isBlurDetected && isEdgeDetected && !isContrastLow;
 		} catch (err) {
 			return true;
 		}
@@ -39,5 +47,6 @@ export default class ImageChecker {
 
 	public destroy(): void {
 		ImageChecker.instance = null;
+		this.blob = null;
 	}
 }
diff --git a/src/modules/ImageManager.ts b/src/modules/ImageManager.ts
index 3a5562e..6803060 100644
--- a/src/modules/ImageManager.ts
+++ b/src/modules/ImageManager.ts
@@ -1,8 +1,7 @@
 export default class ImageManager {
 	private static instance: ImageManager | null = null;
-	private static blob: Blob | null = null;
-	private static blobPromise: Promise<Blob> | null = null;
-	private static resolveBlobPromise: ((blob: Blob) => void) | null = null;
+	private blob: Blob | null = null;
+	private blobSetCallback: ((blob: Blob) => void) | null = null;
 
 	public static getInstance(): ImageManager {
 		if (ImageManager.instance === null) {
@@ -12,31 +11,21 @@ export default class ImageManager {
 	}
 
 	public setImageBlob(file: Blob): void {
-		if (ImageManager.blob == null) {
-			ImageManager.blob = file;
-			if (ImageManager.resolveBlobPromise != null) {
-				ImageManager.resolveBlobPromise(ImageManager.blob);
-				ImageManager.blobPromise = null;
-				ImageManager.resolveBlobPromise = null;
+		if (this.blob == null) {
+			this.blob = file;
+			if (this.blobSetCallback != null) {
+				this.blobSetCallback(this.blob);
 			}
 		}
 	}
 
-	public async getImageBlob(): Promise<Blob> {
-		if (ImageManager.blob != null) {
-			return await Promise.resolve(ImageManager.blob);
-		} else if (ImageManager.blobPromise == null) {
-			ImageManager.blobPromise = new Promise<Blob>(resolve => {
-				ImageManager.resolveBlobPromise = resolve;
-			});
-		}
-		return await ImageManager.blobPromise;
+	public onBlobSet(callback: (blob: Blob) => void): void {
+		this.blobSetCallback = callback;
 	}
 
 	public destroy(): void {
 		ImageManager.instance = null;
-		ImageManager.blob = null;
-		ImageManager.blobPromise = null;
-		ImageManager.resolveBlobPromise = null;
+		this.blob = null;
+		this.blobSetCallback = null;
 	}
 }
diff --git a/src/screens/preProcessing/components/previewElement/index.ts b/src/screens/preProcessing/components/previewElement/index.ts
index a87e849..979e784 100644
--- a/src/screens/preProcessing/components/previewElement/index.ts
+++ b/src/screens/preProcessing/components/previewElement/index.ts
@@ -1,5 +1,5 @@
 import closeSDK from '../../../../lib/closeSDK';
-import ImageChecker from '../../../../modules/ImageChecker';
+import CallbackHandler from '../../../../modules/CallbackHandler';
 import ImageManager from '../../../../modules/ImageManager';
 import Router from '../../../../modules/Router';
 import VideoStreamScreen from '../../../videoStream';
@@ -45,8 +45,6 @@ export default function createPreviewElement(blob: Blob): HTMLDivElement {
 	primaryButton.className = css.primaryButton;
 	primaryButton.innerText = 'Take a new picture';
 	primaryButton.onclick = () => {
-		const imageChecker = ImageChecker.getInstance();
-		imageChecker.destroy();
 		const videoStreamScreen = VideoStreamScreen.getInstance().getElement();
 		const routerManager = Router.getInstance();
 		routerManager.pop();
@@ -58,6 +56,8 @@ export default function createPreviewElement(blob: Blob): HTMLDivElement {
 	secondaryButton.onclick = () => {
 		const imageManager = ImageManager.getInstance();
 		imageManager.setImageBlob(blob);
+		const callbackHandler = CallbackHandler.getInstance();
+		callbackHandler.callOnComplete({ blob });
 		closeSDK();
 	};
 	bottomSectionWrapper.appendChild(primaryButton);
diff --git a/src/screens/preProcessing/index.ts b/src/screens/preProcessing/index.ts
index 5ba8a6f..643e16d 100644
--- a/src/screens/preProcessing/index.ts
+++ b/src/screens/preProcessing/index.ts
@@ -1,8 +1,8 @@
 import closeSDK from '../../lib/closeSDK';
+import CallbackHandler from '../../modules/CallbackHandler';
 import ComponentManager from '../../modules/ComponentManager';
 import ImageChecker from '../../modules/ImageChecker';
 import ImageManager from '../../modules/ImageManager';
-import OpenCVManager from '../../modules/OpenCVManager';
 import loadingPreProcessingChecksElement from './components/loadingPreProcessingChecksElement';
 import createPreviewElement from './components/previewElement';
 import css from './index.module.css';
@@ -20,22 +20,31 @@ export default class PreProcessingScreen extends ComponentManager {
 		wrapper.appendChild(preProcessingChecksElement);
 
 		this.onMount(async () => {
-			const opencvManager = OpenCVManager.getInstance();
-			opencvManager.onLoad(async error => {
-				if (error != null) return;
-				const imageChecker = ImageChecker.getInstance();
+			const imageChecker = ImageChecker.getInstance();
+
+			imageChecker.onBlobSet(async blob => {
 				const isImageQualityGood =
 					await imageChecker.isImageQualityGood();
 
-				const blob = imageChecker.getImageBlob();
-
 				if (isImageQualityGood) {
 					const imageManager = ImageManager.getInstance();
 					imageManager.setImageBlob(blob);
+					const callbackHandler = CallbackHandler.getInstance();
+					callbackHandler.callOnComplete({ blob });
 					closeSDK();
 					return;
 				}
 
+				try {
+					const callbackHandler = CallbackHandler.getInstance();
+					callbackHandler.callOnPreProcessingChecksFailed({
+						blob,
+						message: 'Poor image quality detected.',
+					});
+				} catch (err) {
+					//
+				}
+
 				// image quality is not good
 				// show the image wih ability to i) Take a new picture, ii) Proceed with this image
 				wrapper.removeChild(preProcessingChecksElement);
diff --git a/src/screens/videoStream/button/index.ts b/src/screens/videoStream/button/index.ts
index ab9b68c..9e2f452 100644
--- a/src/screens/videoStream/button/index.ts
+++ b/src/screens/videoStream/button/index.ts
@@ -1,5 +1,4 @@
 import VideoStreamScreen from '..';
-import closeSDK from '../../../lib/closeSDK';
 import FileInputManager from '../../../modules/FileInputManager';
 import ImageChecker from '../../../modules/ImageChecker';
 import ImageManager from '../../../modules/ImageManager';
@@ -9,6 +8,8 @@ import PreProcessingScreen from '../../preProcessing';
 import css from './index.module.css';
 import rightArrow from './assets/rightArrow.svg';
 import createPrimaryActionButton from '../../../components/primaryActionButton';
+import CallbackHandler from '../../../modules/CallbackHandler';
+import closeSDK from '../../../lib/closeSDK';
 
 export default function createButtonElement(): HTMLDivElement {
 	const buttonWrapper = document.createElement('div');
@@ -45,6 +46,8 @@ export default function createButtonElement(): HTMLDivElement {
 					if (error != null) {
 						const imageManager = ImageManager.getInstance();
 						imageManager.setImageBlob(file);
+						const callbackHandler = CallbackHandler.getInstance();
+						callbackHandler.callOnComplete({ blob: file });
 						closeSDK();
 						return;
 					}
diff --git a/tests/unit/CallbackHandler.test.ts b/tests/unit/CallbackHandler.test.ts
new file mode 100644
index 0000000..ffa498f
--- /dev/null
+++ b/tests/unit/CallbackHandler.test.ts
@@ -0,0 +1,84 @@
+import {
+	type OnComplete,
+	type OnPreProcessingChecksFailed,
+} from '../../src/camera/init';
+import CallbackHandler from '../../src/modules/CallbackHandler';
+
+describe('CallbackHandler', () => {
+	const blob = new File([''], 'filename');
+
+	let callbackHandler: CallbackHandler;
+
+	beforeEach(() => {
+		callbackHandler = CallbackHandler.getInstance();
+	});
+
+	afterEach(() => {
+		callbackHandler.destroy();
+	});
+
+	it('should always return the same instance', () => {
+		const instance1 = CallbackHandler.getInstance();
+		const instance2 = CallbackHandler.getInstance();
+		void expect(instance1).toBe(instance2);
+	});
+
+	it('should call onComplete when callOnComplete is called', () => {
+		const onCompleteMock = jest.fn();
+
+		callbackHandler.setOnComplete(onCompleteMock);
+
+		const response: OnComplete = {
+			blob,
+		};
+
+		callbackHandler.callOnComplete(response);
+		void expect(onCompleteMock).toHaveBeenCalledWith(response);
+	});
+
+	it('should call onPreProcessingChecksFailedCallback when callOnPreProcessingChecksFailed is called', () => {
+		const onPreProcessingChecksFailedMock = jest.fn();
+
+		callbackHandler.setOnPreProcessingChecksFailedCallback(
+			onPreProcessingChecksFailedMock
+		);
+		const response: OnPreProcessingChecksFailed = {
+			blob,
+			message: 'test message',
+		};
+
+		callbackHandler.callOnPreProcessingChecksFailed(response);
+		void expect(onPreProcessingChecksFailedMock).toHaveBeenCalledWith(
+			response
+		);
+	});
+
+	it('should throw an error when callOnPreProcessingChecksFailed is called without setting the callback', () => {
+		const response: OnPreProcessingChecksFailed = {
+			blob,
+			message: 'test message',
+		};
+
+		void expect(() => {
+			callbackHandler.callOnPreProcessingChecksFailed(response);
+		}).toThrow('pre-processing callback not set');
+	});
+
+	it('should destroy the callback handler instance', () => {
+		void expect(CallbackHandler.getInstance()).toBe(callbackHandler);
+		callbackHandler.destroy();
+		void expect(CallbackHandler.getInstance()).not.toBe(callbackHandler);
+	});
+
+	it('should not call onComplete if it is not set', () => {
+		console.error = jest.fn();
+
+		const response: OnComplete = {
+			blob,
+		};
+		callbackHandler.callOnComplete(response);
+		void expect(console.error).toHaveBeenCalledWith(
+			'onComplete callback function is not set.'
+		);
+	});
+});
diff --git a/tests/unit/ConfigManager.test.ts b/tests/unit/ConfigManager.test.ts
index 8f795d2..e01bd23 100644
--- a/tests/unit/ConfigManager.test.ts
+++ b/tests/unit/ConfigManager.test.ts
@@ -1,40 +1,100 @@
-import ConfigManager, { type Config } from '../../src/modules/ConfigManager';
+import ConfigManager from '../../src/modules/ConfigManager/ConfigManager';
 
 describe('ConfigManager', () => {
-	it('can be initiated with config and without config', () => {
-		const configManager = ConfigManager.getInstance();
-		void expect(configManager.getConfig()).toEqual(null);
-		configManager.destroy();
+	let configManager: ConfigManager;
 
-		const config = { onboardingInstructions: { timesShown: 2 } };
-		const configManager2 = ConfigManager.getInstance(config);
-		void expect(configManager2.getConfig()).toEqual(config);
-		configManager2.destroy();
+	beforeEach(() => {
+		configManager = ConfigManager.getInstance();
 	});
 
-	it('throws error when config does not match expected type', () => {
+	afterEach(() => {
+		configManager.destroy();
+	});
+
+	it('should throw error if config is invalid', () => {
 		const testCases = [
-			{ something: 'else' },
-			{ onboardingInstructions: 'not an object' },
-			{ onboardingInstructions: { timesShown: 'not a number' } },
-			{ onboardingInstructions: { timesShown: -3 } },
-			{},
-			'test',
+			{
+				config: {
+					onboardingInstructions: '',
+				},
+				expected: '"onboardingInstructions" must be of type object',
+			},
+			{
+				config: {
+					onboardingInstructions: {},
+				},
+				expected: '"onboardingInstructions.timesShown" is required',
+			},
+			{
+				config: {
+					onboardingInstructions: {
+						timesShown: 3,
+					},
+					invalidKey: 'test',
+				},
+				expected: '"invalidKey" is not allowed',
+			},
+			{
+				config: 'asd',
+				expected: '"value" must be of type object',
+			},
+			{
+				config: 0,
+				expected: '"value" must be of type object',
+			},
+			{
+				config: null,
+				expected: '"value" must be of type object',
+			},
 		];
 
-		testCases.forEach(config => {
-			let errorOccurred = false;
+		testCases.forEach(testCase => {
+			const { config, expected } = testCase;
+
+			const oldConfig = configManager.getConfig();
+			void expect(oldConfig).toStrictEqual(null);
+			void expect(() => {
+				// @ts-expect-error: purposefully pass invalid config
+				configManager.setConfig(config);
+			}).toThrow(expected);
+			const newConfig = configManager.getConfig();
+			void expect(newConfig).toStrictEqual(null);
+			configManager.destroy();
+		});
+	});
+
+	it('should return config for a valid config', () => {
+		const testCases = [
+			{
+				config: {
+					onboardingInstructions: {
+						timesShown: 3,
+					},
+				},
+				expected: {
+					onboardingInstructions: {
+						timesShown: 3,
+					},
+				},
+			},
+			{
+				config: {},
+				expected: {},
+			},
+		];
 
-			try {
-				const configManager = ConfigManager.getInstance(
-					config as unknown as Config
-				);
-				configManager.destroy();
-			} catch (error) {
-				errorOccurred = true;
-			}
+		testCases.forEach(testCase => {
+			const configManager2 = ConfigManager.getInstance();
+			const { config, expected } = testCase;
 
-			void expect(errorOccurred).toBe(true);
+			const oldConfig = configManager2.getConfig();
+			void expect(oldConfig).toStrictEqual(null);
+			void expect(() => {
+				configManager2.setConfig(config);
+			}).not.toThrow();
+			const newConfig = configManager2.getConfig();
+			void expect(newConfig).toStrictEqual(expected);
+			configManager2.destroy();
 		});
 	});
 });
diff --git a/tests/unit/ImageChecker.test.ts b/tests/unit/ImageChecker.test.ts
index 0a4d89e..4369fd0 100644
--- a/tests/unit/ImageChecker.test.ts
+++ b/tests/unit/ImageChecker.test.ts
@@ -71,6 +71,22 @@ describe('ImageChecker', () => {
 		}
 	});
 
+	it('calls onBlobSet when image is set', async () => {
+		const checker = ImageChecker.getInstance();
+
+		const file = new File([''], 'filename');
+		checker.setImageBlob(file);
+
+		const onBlobSet = jest.fn();
+
+		checker.onBlobSet(async blob => {
+			onBlobSet();
+			void expect(onBlobSet).toHaveBeenCalled();
+		});
+
+		void expect(onBlobSet).not.toHaveBeenCalled();
+	});
+
 	it('throws an error if no image is provided', async () => {
 		const checker = ImageChecker.getInstance();
 		await expect(checker.isImageQualityGood()).rejects.toThrow(
diff --git a/tests/unit/ImageManager.test.ts b/tests/unit/ImageManager.test.ts
index 6d02ec8..1d65899 100644
--- a/tests/unit/ImageManager.test.ts
+++ b/tests/unit/ImageManager.test.ts
@@ -7,22 +7,24 @@ describe('ImageManager', () => {
 		void expect(instance1).toBe(instance2);
 	});
 
-	it('should not resolve getImageBlob until setImageBlob is called', async () => {
+	it('should call until onBlobSet when setImageBlob is called', async () => {
 		const imageManager = ImageManager.getInstance();
 		let blob;
 
-		const promise = imageManager.getImageBlob().then(res => {
-			blob = res;
-		});
-
 		void expect(blob).toBeUndefined();
 
+		const onBlobSet = jest.fn();
+
 		const file = new File([''], 'filename');
 		imageManager.setImageBlob(file);
 
-		await promise;
+		imageManager.onBlobSet((blob: Blob) => {
+			onBlobSet();
+			void expect(onBlobSet).toHaveBeenCalled();
+			void expect(blob).toBe(file);
+		});
 
-		void expect(blob).toBe(file);
+		void expect(onBlobSet).not.toHaveBeenCalled();
 
 		imageManager.destroy();
 	});
diff --git a/tests/unit/closeSDK.test.ts b/tests/unit/closeSDK.test.ts
index 982e1b7..d87ceb5 100644
--- a/tests/unit/closeSDK.test.ts
+++ b/tests/unit/closeSDK.test.ts
@@ -4,9 +4,10 @@ import { waitFor } from '@testing-library/dom';
 import Router from '../../src/modules/Router';
 import ImageManager from '../../src/modules/ImageManager';
 import HostManager from '../../src/modules/HostManager';
-import ConfigManager from '../../src/modules/ConfigManager';
+import ConfigManager from '../../src/modules/ConfigManager/ConfigManager';
 import LocalStorageManager from '../../src/modules/LocalStorageManager';
 import DocumentScrollController from '../../src/modules/DocumentScrollController';
+import CallbackHandler from '../../src/modules/CallbackHandler';
 
 describe('closeSDK', () => {
 	it('removes host from DOM', async () => {
@@ -24,6 +25,7 @@ describe('closeSDK', () => {
 	it('remove all running instances of the SDK', () => {
 		const router = Router.getInstance();
 		const configManager = ConfigManager.getInstance();
+		const callbackHandler = CallbackHandler.getInstance();
 		const documentScrollController = DocumentScrollController.getInstance();
 		const imageManager = ImageManager.getInstance();
 		const localStorageManager = LocalStorageManager.getInstance();
@@ -39,6 +41,11 @@ describe('closeSDK', () => {
 			.mockImplementation(() => {});
 		void expect(configManagerSpy).not.toHaveBeenCalled();
 
+		const callbackHandlerSpy = jest
+			.spyOn(callbackHandler, 'destroy')
+			.mockImplementation(() => {});
+		void expect(callbackHandlerSpy).not.toHaveBeenCalled();
+
 		const documentScrollControllerEnableScrollSpy = jest
 			.spyOn(documentScrollController, 'enableScroll')
 			.mockImplementation(() => {});
@@ -70,6 +77,7 @@ describe('closeSDK', () => {
 
 		void expect(routerSpy).toHaveBeenCalledTimes(1);
 		void expect(configManagerSpy).toHaveBeenCalledTimes(1);
+		void expect(callbackHandlerSpy).toHaveBeenCalledTimes(1);
 		void expect(
 			documentScrollControllerEnableScrollSpy
 		).toHaveBeenCalledTimes(1);
diff --git a/tests/unit/init.test.ts b/tests/unit/init.test.ts
index 7f9a0ae..daa1741 100644
--- a/tests/unit/init.test.ts
+++ b/tests/unit/init.test.ts
@@ -1,7 +1,16 @@
 import init from '../../src/index';
 
 describe('init', () => {
-	it('rejects for unsupported devices', async () => {
-		await expect(init()).rejects.toThrow('Unsupported device');
+	it('throw an error for unsupported devices', () => {
+		void expect(() => {
+			init(
+				{},
+				{
+					onComplete: () => {
+						//
+					},
+				}
+			);
+		}).toThrow('Unsupported device');
 	});
 });
diff --git a/tests/unit/initRouter.test.ts b/tests/unit/initRouter.test.ts
index 1279cc2..4ae8de9 100644
--- a/tests/unit/initRouter.test.ts
+++ b/tests/unit/initRouter.test.ts
@@ -1,5 +1,5 @@
 import initRouter from '../../src/lib/initRouter';
-import ConfigManager, { type Config } from '../../src/modules/ConfigManager';
+import ConfigManager from '../../src/modules/ConfigManager/ConfigManager';
 import LocalStorageManager from '../../src/modules/LocalStorageManager';
 
 describe('initRouter', () => {
@@ -11,16 +11,8 @@ describe('initRouter', () => {
 		localStorage.clear();
 	});
 
-	it('should throw error when it is called with invalid config', () => {
-		const config = 'invalid config';
-
-		void expect(() => {
-			initRouter(modal, config as unknown as Config);
-		}).toThrow('Invalid config format');
-	});
-
-	it('should return onboardingInstructionsShown as false when it is called with timesShown = 0', () => {
-		const config: Config = {
+	it('should not show onboarding instructions if timeshown is 0', () => {
+		const config = {
 			onboardingInstructions: {
 				timesShown: 0,
 			},
@@ -29,8 +21,8 @@ describe('initRouter', () => {
 		void expect(onboardingInstructionsShown).toBe(false);
 	});
 
-	it('should return onboardingInstructionsShown as true when it is called with timesShown > 0', () => {
-		const config: Config = {
+	it('should show onboarding instructions if timeshown is greater than 1', () => {
+		const config = {
 			onboardingInstructions: {
 				timesShown: 1,
 			},
diff --git a/yarn.lock b/yarn.lock
index a9362b0..a66aab1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -549,6 +549,18 @@
     protobufjs "^7.2.4"
     yargs "^17.7.2"
 
+"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
+  version "9.3.0"
+  resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
+  integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
+
+"@hapi/topo@^5.1.0":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
+  integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
+  dependencies:
+    "@hapi/hoek" "^9.0.0"
+
 "@humanwhocodes/config-array@^0.11.14":
   version "0.11.14"
   resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
@@ -1032,6 +1044,23 @@
     unbzip2-stream "1.4.3"
     yargs "17.7.2"
 
+"@sideway/address@^4.1.5":
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5"
+  integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==
+  dependencies:
+    "@hapi/hoek" "^9.0.0"
+
+"@sideway/formula@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
+  integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
+
+"@sideway/pinpoint@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
+  integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
+
 "@sinclair/typebox@^0.27.8":
   version "0.27.8"
   resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
@@ -5621,6 +5650,17 @@ jmespath@0.16.0:
   resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076"
   integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==
 
+joi@^17.13.1:
+  version "17.13.1"
+  resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.1.tgz#9c7b53dc3b44dd9ae200255cc3b398874918a6ca"
+  integrity sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==
+  dependencies:
+    "@hapi/hoek" "^9.3.0"
+    "@hapi/topo" "^5.1.0"
+    "@sideway/address" "^4.1.5"
+    "@sideway/formula" "^3.0.1"
+    "@sideway/pinpoint" "^2.0.0"
+
 jose@^4.15.5:
   version "4.15.5"
   resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.5.tgz#6475d0f467ecd3c630a1b5dadd2735a7288df706"