Silent Authentication Asynchronous Implementation

Introduction

An asynchronous implementation is very similar to the synchronous method; instead of using the device client to make a series of calls, every part of the workflow is included in webhook callbacks sent to a URL you define.

This guide explains how to implement silent authentication using the asynchronous approach, where your backend sets up a callback to receive the authentication result.

Create an Application

To begin, go to the developer dashboard to create your application:

  • Ensure that Verify is enabled under 'Capabilities'.
  • Configure the Status URL to receive callback events.
  • Generate a JWT using the Application ID and private key of the application.

Trigger Verification

To start the silent authentication process, make a request to /verify. In the following example, the workflow specifies that Verify will first attempt to use Silent Authentication. If the request fails for any reason, it will then fall back to SMS, followed by a voice call OTP.

To run the example, replace the following variables in the sample code with your own values:

Variable Description
JWT Authenticates the API request using JWT.
VERIFY_BRAND_NAME The name of your company or service, shown to the user in the verification message.
VONAGE_APPLICATION_PRIVATE_KEY_PATH Path to the private key of your application.
VONAGE_APPLICATION_ID Application ID of your application.
VERIFY_NUMBER The phone number to send the OTP to, in E.164 format (e.g., +441112233447).

Write the code

Add the following to request.sh:

curl -X POST "https://api.nexmo.com/v2/verify" \
  -H "Authorization: Bearer $JWT"\
  -H 'Content-Type: application/json' \
  -d $'{
	 "brand": "'$VERIFY_BRAND_NAME'",
   "workflow": [
      {
         "channel": "silent_auth",
         "to": "'$VERIFY_NUMBER'"
      },
      {
         "channel": "sms",
         "to": "'$VERIFY_NUMBER'"
      },
      {
         "channel": "voice",
         "to": "'$VERIFY_NUMBER'"
      }
   ]
}'

View full source

Run your code

Save this file to your machine and run it:

sh request.sh

Prerequisites

npm install @vonage/server-sdk @vonage/verify2

Create a file named send-request-with-fallback.js and add the following code:

const { Vonage } = require('@vonage/server-sdk');
const { Channels, SilentAuthChannel } = require('@vonage/verify2');

const vonage = new Vonage({
  applicationId: VONAGE_APPLICATION_ID,
  privateKey: VONAGE_APPLICATION_PRIVATE_KEY_PATH,
});

View full source

Write the code

Add the following to send-request-with-fallback.js:

vonage.verify2.newRequest({
  brand: VERIFY_BRAND_NAME,
  workflow: [
    {
      channel: SilentAuthChannel.SILENT_AUTH,
      to: VERIFY_NUMBER,
    },
    {
      channel: Channels.SMS,
      to: VERIFY_NUMBER,
    },
    {
      channel: Channels.VOICE,
      to: VERIFY_NUMBER,
    },
  ],
})
  .then(({requestId}) => console.log(requestId))
  .catch((err) => console.error(err));

View full source

Run your code

Save this file to your machine and run it:

node send-request-with-fallback.js

Prerequisites

Add the following to build.gradle:

implementation 'com.vonage:server-sdk-kotlin:2.1.1'

Create a file named SendVerificationRequestWithFallback and add the following code to the main method:

val client = Vonage {
    applicationId(VONAGE_APPLICATION_ID)
    privateKeyPath(VONAGE_PRIVATE_KEY_PATH)
}

View full source

Write the code

Add the following to the main method of the SendVerificationRequestWithFallback file:

val response = client.verify.sendVerification(VERIFY_BRAND_NAME) {
    silentAuth(VERIFY_NUMBER)
    sms(VERIFY_NUMBER)
    voice(VERIFY_NUMBER)
}

View full source

Run your code

We can use the application plugin for Gradle to simplify the running of our application. Update your build.gradle with the following:

apply plugin: 'application'
mainClassName = project.hasProperty('main') ? project.getProperty('main') : ''

Run the following gradle command to execute your application, replacing com.vonage.quickstart.kt.verify with the package containing SendVerificationRequestWithFallback:

gradle run -Pmain=com.vonage.quickstart.kt.verify.SendVerificationRequestWithFallback

Prerequisites

Add the following to build.gradle:

implementation 'com.vonage:server-sdk:9.3.1'

Create a file named SendRequestWithFallback and add the following code to the main method:

VonageClient client = VonageClient.builder()
		.applicationId(VONAGE_APPLICATION_ID)
		.privateKeyPath(VONAGE_PRIVATE_KEY_PATH)
		.build();

View full source

Write the code

Add the following to the main method of the SendRequestWithFallback file:

VerificationResponse response = client.getVerify2Client().sendVerification(
		VerificationRequest.builder()
			.addWorkflow(new SilentAuthWorkflow(VERIFY_NUMBER))
			.addWorkflow(new SmsWorkflow(VERIFY_NUMBER))
			.addWorkflow(new VoiceWorkflow(VERIFY_NUMBER))
			.brand(VERIFY_BRAND_NAME).build()
);

View full source

Run your code

We can use the application plugin for Gradle to simplify the running of our application. Update your build.gradle with the following:

apply plugin: 'application'
mainClassName = project.hasProperty('main') ? project.getProperty('main') : ''

Run the following gradle command to execute your application, replacing com.vonage.quickstart.verify2 with the package containing SendRequestWithFallback:

gradle run -Pmain=com.vonage.quickstart.verify2.SendRequestWithFallback

Prerequisites

Install-Package Vonage

Create a file named StartVerificationRequestWithFallback.cs and add the following code:

using Vonage;
using Vonage.Request;
using Vonage.VerifyV2.StartVerification;
using Vonage.VerifyV2.StartVerification.Sms;
using Vonage.VerifyV2.StartVerification.Voice;
using Vonage.VerifyV2.StartVerification.SilentAuth;

View full source

Add the following to StartVerificationRequestWithFallback.cs:

var credentials = Credentials.FromAppIdAndPrivateKeyPath(VONAGE_APPLICATION_ID, VONAGE_APPLICATION_PRIVATE_KEY_PATH);
var client = new VonageClient(credentials);

View full source

Write the code

Add the following to StartVerificationRequestWithFallback.cs:

var request = StartVerificationRequest.Build()
    .WithBrand(BRAND_NAME)
    .WithWorkflow(SilentAuthWorkflow.Parse(TO_NUMBER))
    .WithFallbackWorkflow(SmsWorkflow.Parse(TO_NUMBER))
    .WithFallbackWorkflow(VoiceWorkflow.Parse(TO_NUMBER))
    .Create();
var response = await client.VerifyV2Client.StartVerificationAsync(request);

View full source

Prerequisites

composer require vonage/client

Create a file named request.php and add the following code:

$client = new Vonage\Client(
    new Vonage\Client\Credentials\Keypair(VONAGE_APPLICATION_PRIVATE_KEY_PATH, VONAGE_APPLICATION_ID),
);

View full source

Write the code

Add the following to request.php:

$newRequest = new \Vonage\Verify2\Request\SilentAuthRequest(VERIFY_NUMBER, VERIFY_BRAND_NAME);
$smsWorkflow = new \Vonage\Verify2\VerifyObjects\VerificationWorkflow(\Vonage\Verify2\VerifyObjects\VerificationWorkflow::WORKFLOW_SMS, VERIFY_NUMBER);
$newRequest->addWorkflow($smsWorkflow);
$voiceWorkflow = new \Vonage\Verify2\VerifyObjects\VerificationWorkflow(\Vonage\Verify2\VerifyObjects\VerificationWorkflow::WORKFLOW_VOICE, VERIFY_NUMBER);
$newRequest->addWorkflow($voiceWorkflow);

$client->verify2()->startVerification($newRequest);

View full source

Run your code

Save this file to your machine and run it:

php request.php

Prerequisites

pip install vonage python-dotenv

Write the code

Add the following to send-request-with-fallback.py:

from vonage import Auth, Vonage
from vonage_verify import (SmsChannel, VoiceChannel, SilentAuthChannel,
                           StartVerificationResponse, VerifyRequest)

client = Vonage(
    Auth(
        application_id=VONAGE_APPLICATION_ID,
        private_key=VONAGE_PRIVATE_KEY,
    )
)

verify_request = VerifyRequest(
    brand=VERIFY_BRAND_NAME,
    workflow=[
        SilentAuthChannel(to=VERIFY_NUMBER),
        SmsChannel(to=VERIFY_NUMBER),
        VoiceChannel(to=VERIFY_NUMBER),
    ],
)

response: StartVerificationResponse = client.verify.start_verification(verify_request)
pprint(response)

View full source

Run your code

Save this file to your machine and run it:

python verify/send-request-with-fallback.py

Prerequisites

gem install vonage

Create a file named request.rb and add the following code:

client = Vonage::Client.new(
  application_id: VONAGE_APPLICATION_ID,
  private_key: VONAGE_PRIVATE_KEY
)

View full source

Write the code

Add the following to request.rb:

client.verify2.start_verification(
  brand: VERIFY_BRAND_NAME,
  workflow: [
    {
      channel: 'silent_auth',
      to: VERIFY_NUMBER
    },
    {
      channel: 'sms',
      to: VERIFY_NUMBER
    },
    {
      channel: 'voice',
      to: VERIFY_NUMBER
    }
  ]
)

View full source

Run your code

Save this file to your machine and run it:

ruby request.rb

Response

If the request is successful, the response will return HTTP 200 with the check_url in the body, which we’ll need in the next step:

HTTP/1.1 200 Ok 
{
  "request_id": "470b478f-334c-4f6f-b90d-b44e77ed24bf",
  "check_url": "https://api-eu-4.vonage.com/v2/verify/470b478f-334c-4f6f-aaab-b4a342aed24bf/silent-auth/redirect"
}

If there is an error with the request, such as an incorrectly formatted phone number, the response will contain an HTTP 422 error like this:

HTTP/1.1 422 Unprocessable Entity 
{
  "title": "Invalid params",
  "detail": "The value of one or more parameters is invalid",
  "instance": "b30db5a7-338e-402e-aa5a-40073a9aa07c",
  "type": "https://developer.vonage.com/en/api-errors#invalid-params",
  "invalid_parameters": [
    {
      "name": "workflow[0]",
      "reason": "`to` Phone number is invalid"
    }
  ]
}

Since we are using the asynchronous implementation, in parallel with the API response, you will receive a callback (or webhook) to the URL specified in your dashboard application settings, informing you of the progress of the request.

During the API call to /verify, the Verify API performs internal pre-checks. If everything is in order, the callback will contain the following body:

HTTP/1.1 200 Ok 
{
  "request_id": "21a425df-04b2-43f2-990e-b19ee22e18a0",
  "triggered_at": "2025-07-14T17:01:47.032Z",
  "channel": "silent_auth",
  "status": "action_pending",
  "action": {
    "type": "check",
    "check_url": "https://api-eu-4.vonage.com/v2/verify/21a425df-04b2-43f2-990e-b19ee22e18a0/silent-auth/redirect"
  },
  "type": "event"
}

Until the request expires or is cancelled, check_url will be used to perform a Silent Authentication check by sending the Auth URL. Upon receiving this callback, you must make a GET request to the check_url from the mobile device you are trying to authenticate.

Important: The check_url request must be executed over a mobile data connection (not Wi-Fi). The recommended way to ensure this is to use the Vonage Client SDK (iOS or Android), which automatically routes the request through the device’s cellular network. See the Bypass Wi-Fi for Silent Authentication guide for more details.

If any of these pre-checks fail, for example, due to a network error or an unsupported operator, the callback will look like this:

HTTP/1.1 200 Ok 
{
  request_id: "470b478f-334c-4f6f-b90d-b44e77ed24bf",
  triggered_at: "2025-07-14T16:53:16.965Z",
  channel: "silent_auth",
  status: "failed",
  type: "event"
}

If your /verify request includes a fallback channel, Verify will continue using that channel as shown in the following sequence diagram:

Send Auth URL

Once you've made the GET request, you'll receive one or more HTTP 302 redirects depending on the territory and carrier of the target device:

HTTP/1.1 302 Found
Location: https://eu.api.silentauth.com/phone_check/v0.2/checks/31eaf23d-b2db-4c42-9d1d-e847e75ab330/redirect

Following the redirects will result in either a HTTP 200 or HTTP 4xx response depending on the outcome. If there is a problem with the network, you might see a response like this:

HTTP/1.1 412 Precondition Failed
Content-Type: application/json

{
  "title": "Network not supported",
  "detail": "Device number does not resolve to a supported Mobile Network Operator.",
  "instance": "78e23b55-1633-465e-9325-6abcf186dd00",
  "type": "https://developer.vonage.com/en/api-errors/verify-v2#precondition-failed"
}

A full list of potential error codes can be found in the API Specification.

The Timeout Management section of the Best Practices guide contains useful information for handling situations like network signal or coverage issues during this step.

If the request is valid, you will receive an HTTP 200 response containing your request_id and a code:

{
  "request_id": "c11236f4-00bf-4b89-84ba-88b25df97315",
  "code": "si9sfG"
}

Note: To ensure a secure authentication check and mitigate against a potential man in the middle attack, store the original request_id and compare it with the request_id returned in the response. If the IDs don't match, the silent authentication check should be aborted. See our sample application for an example implementation of how to mitigate against the attack.

Check the Verification Code

Once the end-user receives the code, your client application must send a POST request to the /v2/verify/{request_id} endpoint, replacing {request_id} with the ID you received in the previous call.

To run the example, replace the following variables in the sample code with your own values:

Variable Description
JWT Authenticates the API request using JWT.
VERIFY_REQUEST_ID The request_id received in the previous step.
VONAGE_APPLICATION_PRIVATE_KEY_PATH Private key of your application.
VONAGE_APPLICATION_ID Application ID of your application.
VERIFY_CODE The verification code received by the end-user

Write the code

Add the following to check-verification-code.sh:

curl -X POST "https://api.nexmo.com/v2/verify/$VERIFY_REQUEST_ID" \
  -H "Authorization: Bearer $JWT"\
  -H 'Content-Type: application/json' \
  -d $'{
    "code": "'$VERIFY_CODE'"
  }'

View full source

Run your code

Save this file to your machine and run it:

sh check-verification-code.sh

Prerequisites

npm install @vonage/server-sdk

Create a file named check-verification-code.js and add the following code:

const { Vonage } = require('@vonage/server-sdk');

const vonage = new Vonage ({
  applicationId: VONAGE_APPLICATION_ID,
  privateKey: VONAGE_APPLICATION_PRIVATE_KEY_PATH,
});

View full source

Write the code

Add the following to check-verification-code.js:

vonage.verify2.checkCode(VERIFY_REQUEST_ID, VERIFY_CODE)
  .then((status) => console.log(`The status is ${status}`),
  )
  .catch((err) => console.error(err));

View full source

Run your code

Save this file to your machine and run it:

node check-verification-code.js

Prerequisites

Add the following to build.gradle:

implementation 'com.vonage:server-sdk-kotlin:2.1.1'

Create a file named CheckVerificationCode and add the following code to the main method:

val client = Vonage {
    applicationId(VONAGE_APPLICATION_ID)
    privateKeyPath(VONAGE_PRIVATE_KEY_PATH)
}

View full source

Write the code

Add the following to the main method of the CheckVerificationCode file:

if (client.verify.request(VERIFY_REQUEST_ID).isValidVerificationCode(VERIFY_CODE)) {
    println("Code matches.")
}

View full source

Run your code

We can use the application plugin for Gradle to simplify the running of our application. Update your build.gradle with the following:

apply plugin: 'application'
mainClassName = project.hasProperty('main') ? project.getProperty('main') : ''

Run the following gradle command to execute your application, replacing com.vonage.quickstart.kt.verify with the package containing CheckVerificationCode:

gradle run -Pmain=com.vonage.quickstart.kt.verify.CheckVerificationCode

Prerequisites

Add the following to build.gradle:

implementation 'com.vonage:server-sdk:9.3.1'

Create a file named CheckVerificationCode and add the following code to the main method:

VonageClient client = VonageClient.builder()
		.applicationId(VONAGE_APPLICATION_ID)
		.privateKeyPath(VONAGE_PRIVATE_KEY_PATH)
		.build();

View full source

Write the code

Add the following to the main method of the CheckVerificationCode file:

try {
	client.getVerify2Client().checkVerificationCode(VERIFY_REQUEST_UUID, VERIFY_CODE);
	System.out.println("SUCCESS - code matches!");
}
catch (VerifyResponseException ex) {
	switch (ex.getStatusCode()) {
		case 400: // Code does not match
		case 404: // Already verified or not found
		case 409: // Workflow does not support code
		case 410: // Incorrect code provided too many times
		case 429: // Rate limit exceeded
		default:  // Unknown or internal server error (500)
			ex.printStackTrace();
	}
}

View full source

Run your code

We can use the application plugin for Gradle to simplify the running of our application. Update your build.gradle with the following:

apply plugin: 'application'
mainClassName = project.hasProperty('main') ? project.getProperty('main') : ''

Run the following gradle command to execute your application, replacing com.vonage.quickstart.verify2 with the package containing CheckVerificationCode:

gradle run -Pmain=com.vonage.quickstart.verify2.CheckVerificationCode

Prerequisites

Install-Package Vonage

Create a file named VerifyCodeRequest.cs and add the following code:

using Vonage;
using Vonage.Request;

View full source

Add the following to VerifyCodeRequest.cs:

var credentials = Credentials.FromAppIdAndPrivateKeyPath(VONAGE_APPLICATION_ID, VONAGE_APPLICATION_PRIVATE_KEY_PATH);
var client = new VonageClient(credentials);

View full source

Write the code

Add the following to VerifyCodeRequest.cs:

var request = Vonage.VerifyV2.VerifyCode.VerifyCodeRequest.Build()
    .WithRequestId(REQUEST_ID)
    .WithCode(CODE)
    .Create();
var response = await client.VerifyV2Client.VerifyCodeAsync(request);

View full source

Prerequisites

composer require vonage/client

Create a file named send_code.php and add the following code:

$client = new Vonage\Client(
    new Vonage\Client\Credentials\Keypair(VONAGE_APPLICATION_PRIVATE_KEY_PATH, VONAGE_APPLICATION_ID),
);

View full source

Write the code

Add the following to send_code.php:

try {
    $client->verify2()->check(REQUEST_ID, CODE);
} catch (\Exception $e) {
    var_dump($e->getMessage());
}

View full source

Run your code

Save this file to your machine and run it:

php send_code.php

Prerequisites

pip install vonage python-dotenv

Write the code

Add the following to check-verification-code.py:

from vonage import Auth, Vonage
from vonage_verify import CheckCodeResponse

client = Vonage(
    Auth(
        application_id=VONAGE_APPLICATION_ID,
        private_key=VONAGE_PRIVATE_KEY,
    )
)

response: CheckCodeResponse = client.verify.check_code(
    request_id=VERIFY_REQUEST_ID, code=VERIFY_CODE
)
print(response)

View full source

Run your code

Save this file to your machine and run it:

python verify/check-verification-code.py

Prerequisites

gem install vonage

Create a file named check.rb and add the following code:

client = Vonage::Client.new(
  application_id: VONAGE_APPLICATION_ID,
  private_key: VONAGE_PRIVATE_KEY
)

View full source

Write the code

Add the following to check.rb:

client.verify2.check_code(
  request_id: VERIFY_REQUEST_ID,
  code: VERIFY_CODE
)

View full source

Run your code

Save this file to your machine and run it:

ruby check.rb

Note: a code for a silent authentication workflow can only be checked once.

If the code is valid, you will receive a callback with the status completed:

{
   "request_id": "31eaf23d-b2db-4c42-9d1d-e847e75ab330",
   "status": "completed"
}

Or, if there is an error, you will see 'Invalid Code':

{
   "title": "Invalid Code",
   "type": "https://www.developer.vonage.com/api-errors/verify#invalid-code",
   "detail": "The code you provided does not match the expected value.",
   "instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf"
}

You will then receive a final callback with the result of the check. If successful, you will see a callback with "status": "completed":

{
    "request_id": "c11236f4-00bf-4b89-84ba-88b25df97315",
    "triggered_at": "2020-01-01T14:00:00.000Z",
    "type": "event",
    "channel": "silent_auth",
    "status": "completed",
}

If unsuccessful, for example if the user has been rejected, this will be indicated in the status field:

{
    "request_id": "c11236f4-00bf-4b89-84ba-88b25df97315",
    "triggered_at": "2020-01-01T14:00:00.000Z",
    "type": "event",
    "channel": "silent_auth",
    "status": "user_rejected",
}

At this point, your silent authentication verification is complete.