This guide helps you set up webhooks to receive updates from Passbase


Why Do You Need a Webhook?

Once your user successfully completes a Verification, Passbase will verify and process the identity Resource(s) asynchronously, as the processing is happening at a later time and not directly in response to your code’s execution. Passbase will notify your system about changes in regards to status updates of the Verification so your integration can take subsequent steps.

This is where a webhook comes in place. Passbase can notify you once we have finished processing all the Resources. You can set up webhooks in your developer dashboard's webhook section.

You can find more information about Passbase's integration best-practice in the Overview & Integration Guide section. Below is a graph that summarizes and displays which stage of the processing flow the Passbase system will send a webhook to you.


You can either follow the integration guide or watch the video tutorial that shows how to setup a webhook endpoint in NodeJS. Please be aware that some property or function names might have slightly changed with newer versions. Make sure to compare your implementation with the latest code snippets here in the documentation.

1. Webhook Configuration

To properly receive incoming webhooks, your server should provide an endpoint supporting:

  • TLS encryption version 1.2

  • POST requests are either raw in text/plain if encrypted or application/json if no encryption is activated

You can use a service like e.g. https://webhook.site/ to test receiving webhooks from your developer dashboard. For local testing, you can use a CLI tool such as ngrok to forward the requests to your local development server.

2. Event Types

Our webhook service supports two events, which describe the status of a Verification. Those are:

  • VERIFICATION_COMPLETED: The Verification has been completed processing and is ready to be reviewed in your developer dashboard.

  • VERIFICATION_REVIEWED: The Verification has been approved or declined in your developer dashboard.

Make sure you're not relying on the order of the events inside your application due to networking latency. It is possible that both events can arrive at the same time. Though you can assume that once you receive the VERIFICATION_REVIEWEDevent, VERIFICATION_COMPLETED has fired as well.

Possible statuses

  • pending, this is the default status of a VERIFICATION_COMPLETED event, the Verification is now displayed in your developer dashboard.

  • approved, this is the status when you approve a Verification manually in your developer dashboard or when it passed the auto-review threshold.

  • declined, this is the status when you decline a Verification manually in your developer dashboard or when it failed to pass the auto-review threshold.

If your project has automation thresholds enabled, it is possible that a VERIFICATION_COMPLETED event contains an approved or declined status based on the threshold.

Sample Responses

Verification is Completed

The VERIFICATION_COMPLETED event will be triggered when a Verification has been completed and awaits to be reviewed (accepted or declined) or was automatically accepted/declined by our automation feature. The value of the status property can either be pending if the verification lands in manual review, or approved or, declined if you have automation on.

"key": "b76e244e-26a3-49ef-9c72-3e599bf0b5f2",
"status": "pending",
"created": 1582628711,
"updated": 1582628999,
"processed": 1582628999,

Verification is Reviewed

The VERIFICATION_REVIEWED event will be triggered when a Verification status has changed. This will happen if the Verification has been approved or declined in your dashboard, or the automation has automatically approved or declined based on a threshold set by you (e.g. >80%). The value of the status property can be approved or declined.

After this event, it is time to use the Passbase API, to retrieve the details of this verification with the identity access key. Please take a look at the API section and integration best practices for more information.

"key": "b76e244e-26a3-49ef-9c72-3e599bf0b5f2",
"status": "approved",
"created": 1582628711,
"updated": 1582628999,
"processed": 1582628999,

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 Identity which triggered this webhook. This will help you link back to your user as well as query our backend API for the details of the Identity.



Whether the Verification was approved or declined. This is triggered by you accepting/declining the Verification in the dashboard or Passbase automation does it for you.



UNIX timestamp, when the Verification was created



UNIX timestamp, when the Verification was updated.



UNIX timestamp, when the Verification was processed by Passbase.

3. Security / Encryption

Webhooks are a crucial node to ensure the functionality of your integration, 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-long secret in your webhook configuration, which will be used to encrypt the request body.

Below you'll find an example of how to decipher an incoming webhook request. We assume the variable request_body is the response you received via the webhook. 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(decrypted_result_bytes)
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)

The example server in the next section also shows a working version how to decrypt webhooks.


Example Server

You can find an example where we set up a webhook endpoint using NodeJS on our Github here.

But a webhook endpoint is nothing else than a simple POST endpoint of a server, like in the example below:

app.post("/passbase-webhooks", (req, res) => {
const processWebhook = (request) => {
const webhook = webhookHelper.decryptWebhookIfNeeded(request);
switch (webhook.event) {
// Do logic here for VERIFICATION_COMPLETED event
// Do logic here for VERIFICATION_REVIEWED event
console.log("Couldn't process webhook event");

Serverless with Firebase

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

After you have set it up, you can expose cloud function as an express server endpoint. This sample project shows you the easiest way to set this up. To sum up, you need to configure a cloud function to listen to incoming post requests. In the example below, we listen to an endpoint called 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 this as your wehook endpoint under your Passbase Webhook Settings and trigger a function by e.g. finishing a Verification or approving/declining one, it will fire a webhook to the Google Cloud Function and you can see it in the Firebase 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 project code to it. This article from the official Firebase documentation provides additional details.