Berbix empowers companies to confidently answer the question: “Are you who you say you are?” through ID verification and proprietary fraud detection technology. Our customers embed one of our client SDKs in their apps in order to use the Berbix verification flow, which looks like this:
First, imagine if all the frontend SDKs were built in a traditional manner where each contains all the logic to render the whole verification flow. In this scenario, if we wanted to do something as simple as adding a new button to a page, we’d need to make a code change in every single one of our SDKs. This approach is not only tedious and time consuming, but also error-prone and a maintainability nightmare. At the same time, we need to maintain the native and wrapper SDKs mentioned above in order to ensure that customers with a wide variety of tech stacks can integrate easily with Berbix.
In order to solve this challenge we employ a technique called Backend-Driven Frontend, also known as Server-Driven UI. In this paradigm, the backend tells the frontend what it needs to render at each step. All the frontend knows how to do is build screens from a set of UI components. It has no inherent ID verification logic built in and knows nothing about the overall Berbix flow.
In order to have the backend tell the frontend what it needs to render, the two systems need to agree on a shared language to communicate about UI components. We call this our directive language. When the client SDK starts up it makes a request to the Berbix backend, which will respond with a directive that tells the client SDK how to behave.
A simplified version of our directive language is presented below. One of the key units of a directive is a Screen, which can be represented as the following:
In the example below, we can see the directive that the backend would send to the SDK to render a screen that asks the user to upload the back of their ID.
components array (line 3) describes the UI components that should be laid out on the screen from top to bottom. Below the ‘Back of ID’ text you can see the breadcrumb component, which shows the user’s progress through the verification flow. Line 25 shows an example of how a directive indicates what should happen when a button is pressed. We’ll explore that more in the next section.
Now imagine we wanted to change the above page to have a smaller icon, change the text, and change the action that occurs when the button is clicked. With this system in place, all we need to do is edit the directive on the backend. All of our client-facing experiences will be immediately updated without having to update the SDKs and wait for our customers to upgrade their integrations.
Next, let’s explore how a directive tells the frontend how it should handle a user action like clicking a button or taking a photo. Line 23 of the code snippet in the previous section shows a button component with a field called
target string represents an action that should be carried out when the user clicks the button. Our directive language defines 4 action types.
A fetch action has the form of
action:fetch:name_of_directive. When the frontend encounters a fetch action, it will make a request to the backend to get a new directive.
A submit action has the form
action:submit. This will submit any information that the backend needs to complete the current directive. For example, for a directive that asks the frontend to capture the back of the ID, the frontend would have to submit a photo to the backend to complete the directive. Upon triggering a submit action, the backend will respond with a new directive.
A capture action has the form of
action:capture:name_of_capture. An example of this can be seen in line 25 of the code snippet in the previous section on the ‘Use my camera’ button. The frontend would then look for the capture with the name
name_of_capture in the current directive to determine how the image should be captured. Here’s an example of what a capture might look like along with its corresponding screen:
On line 5, the
camera attribute specifies configurations like whether we should use the front or rear-facing camera on the device, the frame rate to use, and the dimensions of the video capture screen. Next, in the
extractors attribute we specify what we want to look for in the video stream. In this case, we automatically scan and extract the pdf417 barcode. The pdf417 scanner is defined in the
scanners field. Here we are directing the frontend to look for a pdf417 barcode in the video stream every 150ms. Upon detecting a barcode, it will send a submit action to the backend to complete the current directive. If no barcode is found within 25s, it will trigger the timeout action, which in this case brings the user to a new screen.
A screen action has the form of
action:screen:name_of_screen. When the frontend encounters a screen action, it will render the screen defined in the current directive with the name
name_of_screen. Here’s an example:
An important aspect to note about this approach is that the frontend is only capable of drawing components that are defined in the directive language. If, for example, we wanted to introduce a new drop-down component, we’d need to update the backend code to be able to represent a drop-down and update all the SDKs to support the drop-down component.
The need to update the frontend SDKs here introduces a problem. Consider the following scenario: An end user downloads company X’s iOS app, which includes version 1.0 of the Berbix SDK. A week later, Berbix releases version 1.1 of the SDK, which now has support for drop-down components. Berbix also updates the backend to include a drop-down in one of the screens. Company X hasn’t upgraded to SDK version 1.1 yet, so when the end user goes through the Berbix flow and receives a directive containing a screen with a drop-down component, the SDK doesn’t know how to draw the component.
The key to solving this is versioning. When making changes to directives, we need to be mindful of the capabilities of different SDK versions. In the above example, we would have kept the original version of the screen without the drop-down and introduced a new version with the drop-down. Next, we maintain a mapping of SDK version numbers and their capabilities. Lastly, when the client SDK makes a request to the backend, it will include it's SDK version number. In this example, the backend would see that the client is using SDK version 1.0, which does not have support for drop-downs. It’d then respond to the client with the older directive version.
Overall we’ve found this approach to be enormously helpful from both a product and engineering perspective. It allows us to ship new features faster and we no longer have to think much about which features are available across different platforms. In the future, we may expand our directive language to provide even more granular control of image capture and extraction settings. If this work sounds interesting to you, or if you’d like to join our small, talented team solving identity verification at scale, take a look at our open job postings or drop us a note at firstname.lastname@example.org.
Special thanks to Nick Adams, Eric Levine, and Jonathan Unikowski for their help in editing this article and their incredible work designing and building the software discussed here.