This documentation helps you set up webhooks to handle completed authentications

Why Do I Need A Webhook?

Once one of your users successfully finishes an Authentication, you will need to wait for Passbase to process the provided Document. Once finished, you can request the result of that Authentication (e.g. date of birth or first name). But when is the right time to request the details from Passbase? This is where a webhook comes in place. We can notify you once we have finished the processing the Document. Therefore you know now is the right time to send us the request.


In order to properly setup a webhook, your server should provide an endpoint supporting:

  • TLS encryption version 1.2

  • POST requests as raw in text/plain or application/json format

You can use a service like e.g. https://webhook.site/ to try out and receive webhooks from your dashboard.


Our webhook service supports two events, which describe the status of the Authentication once it has been processed by our system:

  • AUTHENTICATION_PROCESSED:The Authentication has been processed and is ready to be reviewed in your dashboard

  • AUTHENTICATION_REVIEW_STATUS_CHANGED:The Authentication has been approved or rejected in your dashboard. This can directly by triggered if you are using our auto accept / reject feature based on a threshold.

Make sure you're not relying on the order of the events inside your application due to networking latency. It could happen that they arrive at the same time. Though you could assume that once you receive theAUTHENTICATION_REVIEW_STATUS_CHANGEDevent,AUTHENTICATION_PROCESSED has fired as well.


Webhooks are crucial node to ensure the functionality of the Passbase integration and, as such, they can be the target of a malicious user aiming to disrupt the service or impersonate your users. In order to protect your application from these threats you must include a 32 bytes (or ASCII characters) long secret in your webhook configuration, which will be used to encrypt the request body.

You'll find examples below on how to decipher the incoming webhook request. We assume the variable request_body to be coming from your backend framework. The cipher initialization vector will be sent over in the first 16 bytes of the response.

const crypto = require("crypto")
// Your response from the webhook.
const request_body = ""
// In the example here it is already a string. In prod it'll be a raw file format of
// text/plain, hence you gotta do request_body.toString() in the next line
const encrypted_result = Buffer.from(request_body, 'base64')
const iv = encrypted_result.slice(0,16)
const cipher = crypto.createDecipheriv('aes-256-cbc', "YOUR_WEBHOOK_SECRET", iv)
const decrypted_result_bytes = Buffer.concat([cipher.update(encrypted_result.slice(16)), cipher.final()])
const decrypted_result = decrypted_result_bytes.toString()
const json_result = JSON.parse(decrypted_result)
from Crypto.Cipher import AES
import base64
import json
request_body = "" # Your response from the webhook
encrypted_result = base64.b64decode(encrypted_webhook)
iv = encrypted_result[:16]
cipher = AES.new("YOUR_WEBHOOK_SECRET".encode("utf8"), AES.MODE_CBC, iv)
decrypted_result_bytes = cipher.decrypt(encrypted_result[16:])
decrypted_result = bytes.decode(
json_result = json.loads(decrypted_result)
require 'openssl'
require 'base64'
require 'json'
request_body = "" # Your response from the webhook
encrypted_result = Base64.decode64(request_body)
cipher = OpenSSL::Cipher::AES256.new(:CBC)
cipher.key = "YOUR_WEBHOOK_SECRET"
cipher.iv = encrypted_result[0..15]
decrypted_result = cipher.update(encrypted_result[16..-1]) + cipher.final
json_result = JSON.parse(decrypted_result)
// OpenSSL is already integrated in PHP
$request_body = ""; // Your response from the webhook
$encrypted_result = base64_decode($request_body);
$iv = substr($encrypted_result, 0, 16);
$decrypted_result = openssl_decrypt(substr($encrypted_result, 16), 'AES-256-CBC', $key, $options=OPENSSL_RAW_DATA, $iv);
$json_result = json_decode($decrypted_result, true);
package main
import (
func main() {
var webhook_response = "" // Your response from the webhook
key := []byte("YOUR_WEBHOOK_SECRET")
decrypted_webhook := decrypt(key, webhook_response)
func decrypt(key []byte, cryptoText string) string {
cipherText, err := base64.StdEncoding.DecodeString(cryptoText)
if err != nil {
block, err := aes.NewCipher(key)
if err != nil {
if len(cipherText) < aes.BlockSize {
panic("cipherText too short")
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(cipherText, cipherText)
return fmt.Sprintf("%s", cipherText)

Using Firebase as Backend

If you are using Google Firebase as your backend, you can also receive webhooks by using Google Cloud Functions. You can learn here how to set up your Firebase project for using Cloud Functions here.

After you have set it up, you can expose an express server as one cloud function endpoint. This sample project shows you the easiest way to set this up. But in the end you need to configure a cloud function to listen to incoming post requests. In the example below we listen to widgets/webhooks:

import * as functions from "firebase-functions";
const crypto = require("crypto");
const express = require("express");
const cors = require("cors");
const app = express();
const admin = require("firebase-admin");
app.use(cors({ origin: true }));
// Receive post request with Express Endpoint
app.post("/webhooks", (req: any, res: any) => {
const incomingWebhook = req.body;
// Do what you want to do with the webhook
// (Optional) - Decrypt webhook if you set up encryption
.then((webhook) => {
// Do what you want to do with the webhook
.catch((err: any) => console.log(err));
const decryptWebhookTest = async (webhookResponse: string) => {
// If needed
// Expose Express API as a single Cloud Function:
exports.widgets = functions.https.onRequest(app);

And by this, you will get a webhook endpoint that looks like this:


If you set now in your Passbase project this as your endpoint under Settings/Webhooks and trigger a function by e.g. finishing an Authentication or rejecting/approving one, it will fire the Google Cloud Function like you can see in the Logs. Now you can do what you want with it, e.g. ping our API or update your user.

If you are struggling, just use our sample project from Github and adjust your own projects code to it. This article from the official Firebase documentation gives also more details.


Authentication is processed

If you have set up a webhook on Passbase and one of your users completes an Authentication, you will receive a response body like below. The AUTHENTICATION_PROCESSED event means that an Authentication by a user has been completed and waits to be reviewed (accepted or rejected) or will now be automatically accepted / rejected by our automation feature.

"authentication_key": "b76e244e-26a3-49ef-9c72-3e599bf0b5f2",
"review_status": null,
"created_at": 1582628711,
"updated_at": 1582628999,
"integration_type": "signup",
"additional_attributes": {
"identifier": "aofbaofib@gmail.com",
"country_code": "us",
"zm_session_id": "asdfa-124-312g-24-23457f39864d",
"identifier_type": "email",
"customer_user_id": "1234567"
"reauthenticated_from_key": null,
"watchlist": null,

Authentication status has changed

This webhook response is fired once an Authentication status has changed. This will happen if the Authentication has been approved or rejected in your dashbaord, or the automation has automatically accepted or rejected based on a threshold set by you (e.g. >90%).

After this event, it is the time to use our API now, to ping the details of this verification with the authentication_key. Please take a look at the API section for this and also compare our best practice implementation.

"authentication_key": "b76e244e-26a3-49ef-9c72-3e599bf0b5f2",
"review_status": true,
"created_at": 1585782482,
"updated_at": 1585782614,
"integration_type": "signup",
"additional_attributes": {
"identifier": "aofbaofib@gmail.com",
"country_code": "us",
"zm_session_id": "asdfa-124-312g-24-23457f39864d",
"identifier_type": "email",
"customer_user_id": "1234567"
"reauthenticated_from_key": null,
"watchlist": null

You can find a further description of the response values below:


Data type




The type of event that triggered this webhook.



The UUID of the Authentication which triggered this webhook. This will help your to link it back to your user as well as query our backend API about the details of the Authentication.


boolean ornull

Whether the Authentication was accepted (true) or rejected (false). This is triggered by you accepting/rejecting the Authentication in the dashboard or your automation does it for you.



The UNIX timestamp when the Authentication which triggered this webhook was created.



The UNIX timestamp of when the Authentication which triggered this webhook was updated.



Under which kind of integration flow was the Authentication created. The possible values for this field are:

signup:Authentication (a full completed identity verificcation)

login:Reauthentication (a user has proven again through completing the selfie that he is the one who he claims to be)



The additionalAttributes submitted by the client to track the user which completed the Authentication. This is useful for your internal implementation.


JSON or null

Will be null if the feature isn't activated or we couldn't run the watchlist, due to missing date of birth, first name or lastname.

If we did run the feature: Will have a property called clean inside the object which has the values either true or false: - If we did not find the person on a watchlist, we return true since his background is clean - If we found the person on a watchlist, the value will be false

A previous version of our webhook contained more information. We deprecated these, hence don't rely on the fields below to be in future versions of the webhook.

Deprecated fields

  • api_key: Used in custom authorization protocols

  • customer_user_id: Contains specific data from additionalAttributesof an Authentication

  • authenticated_at: When the Authentication was either accepted or rejected

  • authenticated: Whether the Authentication was accepted or rejected

  • reviewed_at: When the Authentication was processed

  • authentication_review_status: Same as review_status