Two-factor authentication (2FA) adds a critical layer of security to user verification processes. By requiring not just something the user knows (like a password) but also something they have (like a one-time password (OTP) sent to their phone), you significantly reduce the risk of unauthorized access.
This guide provides a step-by-step walkthrough for implementing SMS-based OTP verification in a Node.js application using the Express framework and the Vonage Verify API. We will build a simple web application that allows users to enter their phone number, receive an OTP via SMS, and verify that code to gain access.
Project Goals:
- Build a simple Node.js/Express web application.
- Integrate Vonage Verify API to send OTPs via SMS.
- Implement endpoints to request an OTP and verify the submitted code.
- Handle basic error scenarios and provide user feedback.
- Secure API credentials and implement basic security measures like rate limiting.
Technologies Used:
- Node.js: JavaScript runtime environment.
- Express: Minimalist web framework for Node.js.
- Vonage Verify API: Service for sending and checking OTPs across various channels (we'll use SMS).
- @vonage/server-sdk: Official Vonage Node.js library for interacting with the API.
- dotenv: Module to load environment variables from a
.env
file. - EJS: Simple templating engine for rendering HTML views.
- Express Rate Limit: Middleware for basic rate limiting.
- body-parser: Middleware to parse incoming request bodies (though modern Express has built-ins).
System Architecture:
graph LR
A[User's Browser] -- 1. Enters Phone Number --> B(Express App);
B -- 2. POST /request-verification --> C(Vonage Verify API);
C -- 3. Sends SMS OTP --> D[User's Phone];
A -- 4. Enters OTP --> B;
B -- 5. POST /check-verification (with OTP & Request ID) --> C;
C -- 6. Verification Result --> B;
B -- 7. Renders Success/Failure --> A;
(Note: The rendering of this diagram depends on the platform where this article is viewed. It uses Mermaid syntax.)
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. Download Node.js
- Vonage API Account: Required to get API credentials. Sign up for a free Vonage account - you'll get free credit to start.
- Basic understanding of Node.js, Express, and asynchronous JavaScript.
Final Outcome:
By the end of this guide, you will have a functional Node.js application capable of sending OTPs via SMS and verifying them, including essential security considerations and error handling.
GitHub Repository:
Find the complete working code for this tutorial here: GitHub Repository
1. Setting Up the Project
Let's start by creating our project directory, initializing Node.js, and installing the necessary dependencies.
Steps:
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it:
mkdir node-vonage-2fa cd node-vonage-2fa
-
Initialize Node.js Project: Initialize the project using npm (this creates a
package.json
file):npm init -y
-
Install Dependencies: Install Express for the web server, the Vonage SDK,
dotenv
for environment variables,ejs
for templating,body-parser
for form parsing, andexpress-rate-limit
for security:npm install express @vonage/server-sdk dotenv ejs body-parser express-rate-limit
express
: Web framework.@vonage/server-sdk
: To interact with the Vonage APIs.dotenv
: To load environment variables from a.env
file.ejs
: Templating engine to render HTML views with dynamic data.body-parser
: Middleware to parse incoming request bodies (needed for form submissions).express-rate-limit
: Basic request throttling.
-
Create Project Structure: Set up a basic directory structure:
node-vonage-2fa/ ├── views/ │ ├── index.ejs │ ├── verify.ejs │ └── success.ejs ├── .env ├── .gitignore ├── server.js └── package.json
-
Configure Environment Variables (
.env
): Create a file named.env
in the project root. This file will store sensitive information like your API keys. Never commit this file to version control.- Obtain Vonage Credentials:
- Log in to your Vonage API Dashboard.
- On the main dashboard page, you will find your API key and API secret under the "API settings" section or a similar area.
- Add to
.env
:Replace# .env VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET VONAGE_BRAND_NAME=MyApp # Optional: Name shown in the SMS message template PORT=3000 # Optional: Port the server will run on
YOUR_API_KEY
andYOUR_API_SECRET
with your actual credentials.VONAGE_BRAND_NAME
is used in the SMS message (e.g., "Your MyApp code is: 1234").
- Obtain Vonage Credentials:
-
Create
.gitignore
: Create a.gitignore
file in the root directory to prevent sensitive files and unnecessary folders from being committed to Git:# .gitignore node_modules/ .env npm-debug.log* yarn-debug.log* yarn-error.log*
-
Basic Server Setup (
server.js
): Create the mainserver.js
file and set up a basic Express server:// server.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const bodyParser = require('body-parser'); // To parse form data const path = require('path'); const { Vonage } = require('@vonage/server-sdk'); const app = express(); const port = process.env.PORT || 3000; // --- Middleware Setup --- app.use(bodyParser.json()); // Parse JSON bodies app.use(bodyParser.urlencoded({ extended: true })); // Parse URL-encoded bodies (from forms) // Note: Modern Express versions (4.16+) include built-in middleware: // app.use(express.json()); // app.use(express.urlencoded({ extended: true })); // Using these removes the need for the 'body-parser' dependency. // Set view engine to EJS app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); // Specify the views directory // --- Vonage Client Initialization --- const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET }); // --- Basic Route --- app.get('/', (req, res) => { // Render the initial page (we'll create index.ejs next) res.render('index', { error: null }); }); // --- Start Server --- app.listen(port, () => { console.log(`Server listening on port ${port}`); });
require('dotenv').config();
: Loads variables from.env
intoprocess.env
.bodyParser
: Parses incoming form data. (Note added about modern Express alternatives).app.set('view engine', 'ejs')
: Configures EJS as the templating engine.new Vonage(...)
: Initializes the Vonage SDK client using credentials from.env
.
2. Implementing Core Functionality (OTP Request & Verification)
Now, let's build the core logic: requesting an OTP and verifying the code entered by the user.
Step 2.1: Create the Initial View (views/index.ejs
)
This view will contain a simple form for the user to enter their phone number.
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width_ initial-scale=1.0"">
<title>Enter Phone Number</title>
<style>
/* Basic styling for clarity */
/* Note: For larger applications, it's recommended to move CSS */
/* into separate .css files for better organization and maintainability. */
body { font-family: sans-serif; padding: 20px; }
label, input, button { display: block; margin-bottom: 10px; }
.error { color: red; margin-bottom: 15px; }
</style>
</head>
<body>
<h1>Enter Your Phone Number for Verification</h1>
<% if (error) { %>
<p class=""error"">Error: <%= error %></p>
<% } %>
<form action=""/request-verification"" method=""POST"">
<label for=""number"">Phone Number (E.164 format, e.g., 14155552671):</label>
<input type=""tel"" id=""number"" name=""number"" required placeholder=""14155552671"">
<button type=""submit"">Send Verification Code</button>
</form>
</body>
</html>
- Displays an error message if one is passed from the server.
- A form
POST
s the phone number to the/request-verification
endpoint. - Uses E.164 format for phone numbers (country code + number, no symbols).
Step 2.2: Implement the OTP Request Endpoint (server.js
)
Add a new route in server.js
to handle the form submission from index.ejs
. This route will call the Vonage Verify API to start a verification request.
// server.js (add this inside the file, before app.listen)
// --- Route to Request Verification ---
app.post('/request-verification', async (req, res) => {
const phoneNumber = req.body.number;
const brand = process.env.VONAGE_BRAND_NAME || 'MyApp'; // Use brand from .env or default
// Basic validation (more robust validation is recommended)
if (!phoneNumber || !/^\d{10,15}$/.test(phoneNumber.replace(/\D/g, ''))) {
return res.render('index', { error: 'Invalid phone number format. Please use E.164 format (e.g., 14155552671).' });
}
console.log(`Requesting verification for: ${phoneNumber}`);
try {
const response = await vonage.verify.start({
number: phoneNumber,
brand: brand,
// workflow_id: 1 // Optional: Default is SMS -> TTS -> TTS. See Vonage docs for options.
// code_length: 6 // Optional: Default is 4
});
console.log('Vonage Verify API Response:', response);
if (response.status === '0') {
// Successfully initiated verification
const requestId = response.request_id;
// Redirect to the verification page, passing the request ID
res.render('verify', { requestId: requestId, error: null });
} else {
// Handle Vonage API errors (e.g., invalid number, throttled)
console.error(`Vonage Verify Error: Status ${response.status} - ${response.error_text}`);
// Render the index page again with the specific error from Vonage
res.render('index', { error: `Verification failed: ${response.error_text} (Status: ${response.status})` });
}
} catch (error) {
// Handle network errors or other exceptions
console.error('Error calling Vonage Verify API:', error);
res.render('index', { error: 'An unexpected error occurred. Please try again later.' });
}
});
- Retrieves the
number
from the form data (req.body
). - Performs basic validation on the phone number format.
- Calls
vonage.verify.start()
with the phone number and brand name. - Crucially, it checks the
response.status
. A status of'0'
indicates success. - If successful, it extracts the
request_id
from the response. This ID is essential for the next step (checking the code). - It renders the
verify.ejs
view (created next), passing therequestId
. - If
response.status
is not'0'
, it displays theerror_text
provided by Vonage back on the initial form. - Includes a
try...catch
block for network or unexpected errors.
Step 2.3: Create the Verification View (views/verify.ejs
)
This view allows the user to enter the OTP code they received via SMS. It includes a hidden field to pass the requestId
along.
<!-- views/verify.ejs -->
<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width_ initial-scale=1.0"">
<title>Enter Verification Code</title>
<style>
/* Basic styling for clarity */
body { font-family: sans-serif; padding: 20px; }
label, input, button { display: block; margin-bottom: 10px; }
.error { color: red; margin-bottom: 15px; }
</style>
</head>
<body>
<h1>Enter the Code Sent to Your Phone</h1>
<% if (error) { %>
<p class=""error"">Error: <%= error %></p>
<% } %>
<form action=""/check-verification"" method=""POST"">
<input type=""hidden"" name=""requestId"" value=""<%= requestId %>"">
<label for=""code"">Verification Code:</label>
<input type=""text"" id=""code"" name=""code"" required pattern=""\d{4_6}"" title=""Enter the 4 or 6 digit code"">
<button type=""submit"">Verify Code</button>
</form>
</body>
</html>
- Includes a hidden input field (
type=""hidden""
) namedrequestId
. Itsvalue
is set to therequestId
passed from the server (<%= requestId %>
). This ensures the ID is submitted along with the code. - Provides a field for the user to enter the
code
. - The form
POST
s to the/check-verification
endpoint.
Step 2.4: Implement the Code Check Endpoint (server.js
)
Add another route in server.js
to handle the submission from verify.ejs
. This route calls the Vonage Verify API to check if the provided code is correct for the given request_id
.
// server.js (add this inside the file, before app.listen)
// --- Route to Check Verification Code ---
app.post('/check-verification', async (req, res) => {
const requestId = req.body.requestId;
const code = req.body.code;
// Basic validation
if (!requestId || !code || !/^\d{4,6}$/.test(code)) {
// If validation fails, re-render the verify page with an error
// Important: Pass the requestId back so the form still works!
return res.render('verify', { requestId: requestId, error: 'Invalid input. Please enter the 4 or 6 digit code.' });
}
console.log(`Checking verification for Request ID: ${requestId} with Code: ${code}`);
try {
const response = await vonage.verify.check(requestId, code);
console.log('Vonage Check API Response:', response);
if (response.status === '0') {
// Verification successful!
console.log('Verification successful for Request ID:', requestId);
// Render a success page
res.render('success');
} else {
// Verification failed (e.g., wrong code, expired request)
console.error(`Vonage Check Error: Status ${response.status} - ${response.error_text}`);
// Re-render the verification page with the specific error
// Pass the requestId again so the user can retry
res.render('verify', {
requestId: requestId,
error: `Verification failed: ${response.error_text} (Status: ${response.status}). Please try again.`
});
}
} catch (error) {
// Handle network errors or other exceptions
console.error('Error calling Vonage Check API:', error);
// You might want a more specific error page or redirect
res.render('verify', {
requestId: requestId,
error: 'An unexpected error occurred while checking the code. Please try again later.'
});
}
});
- Retrieves the
requestId
andcode
from the form data. - Calls
vonage.verify.check()
with therequestId
andcode
. - Checks
response.status
. If'0'
, the code is correct. Render thesuccess.ejs
view. - If not
'0'
, the code is incorrect, the request expired, or another error occurred. Render theverify.ejs
view again, passing back the samerequestId
and theerror_text
from Vonage, allowing the user to retry with the correct code. - Includes
try...catch
for unexpected errors.
Step 2.5: Create the Success View (views/success.ejs
)
A simple page to indicate successful verification.
<!-- views/success.ejs -->
<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width_ initial-scale=1.0"">
<title>Verification Successful</title>
<style>
body { font-family: sans-serif; padding: 20px; text-align: center; }
h1 { color: green; }
</style>
</head>
<body>
<h1>Verification Successful!</h1>
<p>You have successfully verified your phone number.</p>
<p><a href="" />Start Over</a></p>
</body>
</html>
3. Building a Complete API Layer
In this simple example, the Express routes (/
, /request-verification
, /check-verification
) are the API layer. For more complex applications, you might separate this logic into dedicated API controllers and services.
Testing with curl
:
While browser testing is straightforward, you can also test the endpoints using curl
:
-
Start the server:
node server.js
-
Request Verification:
curl -X POST http://localhost:3000/request-verification \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "number=YOUR_PHONE_NUMBER" # Replace with your actual number in E.164 format
(This will return HTML, but you should receive an SMS.)
-
Check Verification (Get the
requestId
from the server console logs or the HTML response):curl -X POST http://localhost:3000/check-verification \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "requestId=YOUR_REQUEST_ID&code=CODE_FROM_SMS" # Replace with actual values
(Again, this returns HTML indicating success or failure.)
4. Integrating with Third-Party Services (Vonage)
Integration with Vonage is central to this guide. The key steps were covered during setup and implementation:
- Configuration (
.env
): StoringVONAGE_API_KEY
,VONAGE_API_SECRET
, andVONAGE_BRAND_NAME
securely. Obtain these from your Vonage API Dashboard. - SDK Initialization (
server.js
): Creating theVonage
client instance using your credentials. - API Calls (
server.js
): Usingvonage.verify.start()
to send the OTP andvonage.verify.check()
to verify the user's input code.
Fallback Mechanisms:
The Vonage Verify API has built-in workflow options (e.g., SMS -> Text-to-Speech call), configurable via the workflow_id
parameter in verify.start
.
For application-level fallbacks (e.g., handling if the Vonage service is temporarily unavailable), the code in this guide does not implement them, but you would need to consider:
- Catching specific network errors or 5xx server errors from the Vonage SDK calls.
- Implementing a retry mechanism (potentially with exponential backoff) for transient errors.
- Possibly offering an alternative verification method (like email) if Vonage fails consistently, which would require significant additional implementation.
5. Implementing Error Handling and Logging
We've included basic error handling by checking response.status
from Vonage API calls and using try...catch
blocks.
Consistent Strategy:
- Catch API Errors: Check
response.status
fromverify.start
andverify.check
. If not'0'
, useresponse.error_text
to provide feedback to the user (usually by re-rendering the form with an error message). - Catch Network/SDK Errors: Use
try...catch
around API calls to handle unexpected issues. Log these errors and show a generic error message to the user. - Input Validation Errors: Validate user input (phone number, code format) before calling the Vonage API. Provide clear error messages.
Enhanced Logging (Alternative to console.log
):
While this guide uses console.log
and console.error
for simplicity, production applications should use a structured logging library like Winston or Pino. Here's a basic Winston setup example (this is not integrated into the main server.js
code provided earlier, but shows how you could enhance it):
npm install winston
// Example Winston setup (place near the top of server.js if used)
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
// - Write all logs with importance level of `error` or less to `error.log`
// - Write all logs with importance level of `info` or less to `combined.log`
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// If we're not in production then log to the `console`
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
// To integrate this, you would replace calls like:
// console.log(`Requesting verification for: ${phoneNumber}`);
// with:
// logger.info(`Requesting verification for: ${phoneNumber}`);
// and:
// console.error('Error calling Vonage Verify API:', error);
// with:
// logger.error('Error calling Vonage Verify API:', { message: error.message, stack: error.stack });
Retry Mechanisms:
Generally not required for the user-facing OTP flow itself, as Vonage handles retries/fallbacks within its workflow. Application-level retries might be useful only for transient network errors when calling the Vonage API, using libraries like async-retry
with caution (exponential backoff, only retry specific error types like network timeouts or 5xx).
6. Creating a Database Schema and Data Layer
This guide focuses on the OTP flow and does not persist data. In a real-world application integrating 2FA:
- User Context: 2FA is typically initiated after primary authentication (e.g., password login) for an existing user.
- Storing
requestId
Securely: The tutorial passes therequestId
via a hidden form field for simplicity. This is not ideal for security. A better approach is to store therequestId
server-side, associated with the user's session or a temporary database record, and retrieve it when the verification code is submitted.- Example Schema (Conceptual):
-- Example table to temporarily store pending verification requests CREATE TABLE pending_verifications ( id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id), -- Link to your user table vonage_request_id VARCHAR(50) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, -- e.g., NOW() + 5 minutes created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
- When
/check-verification
is called, you'd look up therequestId
based on the user's session instead of taking it from the form.
- Example Schema (Conceptual):
- User Profile: Once verified, store the user's phone number securely in their main profile table.
7. Adding Security Features
Security is critical for authentication.
-
Input Validation/Sanitization:
- We included basic regex checks. Use robust libraries like
express-validator
for production. - Always sanitize or validate data received from the client.
- Standardize phone numbers to E.164 format before sending to Vonage.
- We included basic regex checks. Use robust libraries like
-
Rate Limiting:
- Essential to prevent brute-force attacks (guessing codes) and SMS toll fraud (spamming OTP requests).
- We installed
express-rate-limit
. Let's integrate it intoserver.js
:
// server.js (add near the top, after other requires) const rateLimit = require('express-rate-limit'); // --- Initialize Rate Limiters --- // Rate limiter for requesting verification codes const requestVerificationLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // Limit each IP to 5 requests per windowMs message: 'Too many verification requests from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // Rate limiter for checking verification codes const checkVerificationLimiter = rateLimit({ windowMs: 5 * 60 * 1000, // 5 minutes max: 10, // Limit each IP to 10 check attempts per windowMs (allows for typos) message: 'Too many verification attempts from this IP, please try again after 5 minutes', standardHeaders: true, legacyHeaders: false, }); // --- Apply Rate Limiters to Routes --- // Modify the existing app.post routes to include the middleware // Apply limiter to the request verification route app.post('/request-verification', requestVerificationLimiter, async (req, res) => { // ... (existing route handler logic remains the same) const phoneNumber = req.body.number; const brand = process.env.VONAGE_BRAND_NAME || 'MyApp'; if (!phoneNumber || !/^\d{10,15}$/.test(phoneNumber.replace(/\D/g, ''))) { return res.render('index', { error: 'Invalid phone number format. Please use E.164 format (e.g., 14155552671).' }); } console.log(`Requesting verification for: ${phoneNumber}`); try { const response = await vonage.verify.start({ number: phoneNumber, brand: brand }); console.log('Vonage Verify API Response:', response); if (response.status === '0') { res.render('verify', { requestId: response.request_id, error: null }); } else { console.error(`Vonage Verify Error: Status ${response.status} - ${response.error_text}`); res.render('index', { error: `Verification failed: ${response.error_text} (Status: ${response.status})` }); } } catch (error) { console.error('Error calling Vonage Verify API:', error); res.render('index', { error: 'An unexpected error occurred. Please try again later.' }); } }); // Apply limiter to the check verification route app.post('/check-verification', checkVerificationLimiter, async (req, res) => { // ... (existing route handler logic remains the same) const requestId = req.body.requestId; const code = req.body.code; if (!requestId || !code || !/^\d{4,6}$/.test(code)) { return res.render('verify', { requestId: requestId, error: 'Invalid input. Please enter the 4 or 6 digit code.' }); } console.log(`Checking verification for Request ID: ${requestId} with Code: ${code}`); try { const response = await vonage.verify.check(requestId, code); console.log('Vonage Check API Response:', response); if (response.status === '0') { console.log('Verification successful for Request ID:', requestId); res.render('success'); } else { console.error(`Vonage Check Error: Status ${response.status} - ${response.error_text}`); res.render('verify', { requestId: requestId, error: `Verification failed: ${response.error_text} (Status: ${response.status}). Please try again.` }); } } catch (error) { console.error('Error calling Vonage Check API:', error); res.render('verify', { requestId: requestId, error: 'An unexpected error occurred while checking the code. Please try again later.' }); } }); // ... (rest of server.js, including app.listen)
- Adjust
windowMs
andmax
based on expected usage patterns and security posture.
-
Secure Credential Storage: Use
.env
and.gitignore
diligently. Never commit API keys or secrets to version control. Use environment variables in deployment. -
HTTPS: Always deploy applications handling sensitive data over HTTPS to encrypt data in transit.
-
Vonage Security Features: Vonage has its own internal fraud detection and velocity limits that provide an additional layer of protection.
8. Handling Special Cases
- International Phone Numbers: Vonage Verify requires E.164 format (e.g.,
+14155552671
becomes14155552671
,+447700900000
becomes447700900000
). Ensure your application standardizes numbers to this format before sending them. - SMS Delivery Issues: SMS is reliable but not instantaneous or guaranteed. Inform users that codes might take a moment to arrive. Vonage's built-in workflows can use Text-to-Speech calls as fallbacks. Consider UI elements like a ""Resend Code"" button (requires careful implementation, potentially using the Cancel API - see Section 11).
- User Input Errors: Handle code entry typos gracefully by allowing retries (within rate limits). Provide clear error messages from Vonage (e.g., ""Incorrect code entered"").
- Concurrent Requests: Vonage prevents multiple verification requests to the same number within a short period (typically ~30 seconds), returning
status: '10'
. Handle this error by informing the user to wait before trying again.
9. Implementing Performance Optimizations
For this specific OTP flow, reliability and security usually outweigh raw performance needs.
- Caching: Not applicable for the core OTP request/check steps.
- Resource Usage: Keep Node.js non-blocking using
async/await
correctly. If using a database forrequestId
storage, ensure efficient queries. - Load Testing: Use tools like
k6
orloadtest
to simulate traffic, test rate limiting effectiveness, and identify potential bottlenecks under load.
10. Adding Monitoring, Observability, and Analytics
- Health Checks: Implement a basic health check endpoint for monitoring systems:
// server.js app.get('/health', (req, res) => { res.status(200).send('OK'); });
- Performance Metrics: Monitor Node.js process metrics (CPU, memory, event loop latency) using tools like
pm2
or platform-integrated monitoring (CloudWatch, Google Cloud Monitoring, Datadog, etc.). - Error Tracking: Use services like Sentry, Bugsnag, or Datadog APM to capture, aggregate, and alert on runtime errors.
- Key Metrics Dashboard: Monitor business and operational metrics:
- Verification requests initiated vs. completed successfully vs. failed.
- Breakdown of failure reasons (Vonage status codes).
- Latency of Vonage API calls (
verify.start
,verify.check
). - Rate limit trigger counts.
- Vonage Dashboard: Use the Vonage API Dashboard to inspect logs for individual API calls, check SMS delivery status, and diagnose errors reported by the API.
11. Troubleshooting and Caveats
- Common Vonage Verify Status Codes: Familiarize yourself with common statuses returned by
verify.start
andverify.check
:0
: Success.1
: Throttled (Too many requests).2
: Missing parameters.3
: Invalid parameters (e.g., bad number format).4
: Invalid credentials (Check API Key/Secret).5
: Internal Vonage error.6
: Invalid Request ID (Expired, already verified, or incorrect).9
: Partner quota exceeded.10
: Concurrent verifications to the same number (Wait ~30s).15
: Number barred / cannot receive SMS / invalid number.16
: Wrong code entered by user.17
: Code expired (User took too long, default ~5 mins).- See the Vonage Verify API documentation for a full list.
- Vonage Cancel API: If you need to allow users to cancel an ongoing verification (e.g., before requesting a new code, or if they close the verification UI), you can use the
verify.control
method.- Call
vonage.verify.control(requestId, 'cancel')
. - Check the response status. A status
'19'
often indicates a successful cancellation according to recent docs, but check the specific SDK/API documentation for confirmation.
- Call