Frequently Asked Questions
Integrate Sinch's Verification API into your Node.js application using their provided SDK. This guide demonstrates a step-by-step implementation using Fastify, a high-performance Node.js framework, and TypeScript for improved code maintainability.
Sinch Verification API simplifies OTP generation, delivery via SMS or voice, and verification, offloading tasks like number formatting and carrier deliverability. It enhances security by managing the complexities of OTP lifecycles, improving implementation reliability.
Fastify is a performant and developer-friendly Node.js framework. Its speed and efficiency make it an ideal choice for building a robust and scalable 2FA system with Sinch. The guide leverages Fastify's plugin system for cleaner code organization.
Initialize a Node.js project, install Fastify, TypeScript dependencies, the Sinch SDK, Prisma ORM, and relevant type definitions. Configure a tsconfig.json file and define npm scripts for building, starting, development, and testing.
The guide uses PostgreSQL as the database and Prisma as an ORM for simplified database interactions. Prisma's type safety and migration features contribute to better code maintainability and reduced errors.
Leverage Sinch's dedicated service for OTP generation, delivery, and verification anytime you need to implement 2FA (Two-Factor Authentication) in your Node.js applications to streamline the implementation and enhance security.
The architecture involves the end-user, a frontend app, a Fastify backend, the Sinch platform, and a database. The user interacts with the frontend, which communicates with the Fastify backend. The backend integrates with Sinch for OTP delivery and verification, and Prisma connects to the database.
The user initiates login, the backend verifies credentials, and then calls Sinch to initiate SMS verification. Sinch sends the OTP, the user enters it, and the backend verifies it with Sinch. Upon successful verification, the backend updates the user's session and grants access.
While the example uses PostgreSQL with Prisma, you can adapt the guide to use other databases. Ensure you have the appropriate database driver and adjust the Prisma schema and database connection settings accordingly.
The Fastify backend calls the Sinch SDK's report
(verify) function to verify the OTP entered by the user against the phone number and initial verification request. The backend receives a success or failure response from Sinch.
Zod is used for schema validation and type safety. It ensures that all environment variables and request bodies are correctly formatted, minimizing runtime errors by catching type mismatches early in the development process.
The Sinch plugin might throw errors directly, or the guide recommends using a try-catch block to catch specific Sinch error codes within the verification handler. Mapping Sinch error codes to user-friendly messages improves the user experience.
Use bcrypt, a robust password hashing library, to securely store user passwords. The example provides utility functions for hashing and comparing passwords. Never store passwords in plain text.
You need Node.js v18 or later, npm/yarn, a Sinch account with a Verification App, a mobile phone for testing, basic understanding of TypeScript and REST APIs, and optionally, Docker and Docker Compose for a local PostgreSQL database.
Build SMS OTP Two-Factor Authentication with Sinch and Fastify
Learn how to build secure SMS-based two-factor authentication (2FA) using Sinch Verification API within a Fastify Node.js application. This comprehensive guide covers everything from project setup and Sinch SMS OTP integration to implementing secure JWT authentication, error handling, and deployment best practices for production environments.
Important Security Note: SMS-based OTP has known vulnerabilities (SIM swapping, SMS interception, phishing). Per OWASP guidelines, SMS 2FA should not be the sole protection for applications containing Personally Identifiable Information (PII) or financial data. Consider TOTP authenticator apps or hardware tokens for high-security applications. This guide demonstrates SMS 2FA implementation for educational purposes and moderate-security use cases.
Target Audience: Developers familiar with Node.js and potentially Fastify, looking to add robust SMS OTP verification to their applications. Goal: Build a production-ready Fastify API that handles user registration, login, and secures sessions using Sinch SMS OTP verification. Outcome: A secure, documented, and testable API backend with SMS 2FA.
What You Will Build:
Why This Approach?
System Architecture:
Prerequisites:
async/await
.curl
or a tool like Postman/Insomnia for API testing.1. Fastify Project Setup with TypeScript and Sinch SDK
Let's initialize our Fastify project using TypeScript and set up the basic structure.
1.1 Initialize Project & Install Dependencies
Open your terminal and run the following commands:
1.2 Configure TypeScript (
tsconfig.json
)Create a
tsconfig.json
file in the project root:Why these settings?
extends
: Inherits sensible defaults for Node.js 20 (current LTS).outDir
,rootDir
: Define project structure for source and compiled files.module
:CommonJS
is standard for Node.js unless you're specifically setting up ES Modules.strict
: Catches more potential errors during development.esModuleInterop
,forceConsistentCasingInFileNames
,skipLibCheck
: Common settings for compatibility and faster builds.sourceMap
: Crucial for debugging compiled code.1.3 Configure Development Scripts (
package.json
)Add the following scripts to your
package.json
:build
: Compiles TypeScript to JavaScript.start
: Runs the compiled JavaScript application (for production).dev
: Runs the application usingts-node
andnodemon
for automatic restarts during development.test
: Command to run automated tests (example uses Vitest).prisma:*
: Commands for managing database migrations and generating the Prisma client.1.4 Project Structure
Create the following directory structure:
fastify-sinch-otp/
prisma/
schema.prisma
# Prisma schema definitionmigrations/
# Database migration files (generated)src/
modules/
# Feature modules (e.g., auth, users)auth/
auth.controller.ts
auth.routes.ts
auth.schema.ts
# Zod schemas & type definitionsauth.service.ts
plugins/
# Fastify plugins (e.g., db client, auth)prisma.ts
sinch.ts
jwtAuth.ts
config/
# Configuration files/logicindex.ts
types/
# Global or shared type definitionsfastify-jwt.d.ts
# JWT type augmentationsutils/
# Utility functionshash.ts
app.ts
# Main Fastify application setupserver.ts
# Entry point, starts the server.env
# Environment variables (ignored by git).env.example
# Example environment variables.gitignore
package.json
package-lock.json
tsconfig.json
Why this structure?
auth
) into modules makes the codebase easier to navigate and maintain.types
directory for global type definitions and augmentations (like for Fastify plugins).1.5 Environment Variables (
.env
and.env.example
)Create
.env.example
with placeholders:Create a
.env
file (copy from.env.example
) and fill in your actual development values. Crucially, add.env
to your.gitignore
file to prevent committing secrets.Why
.env
? Keeps sensitive information (API keys, database URLs, secrets) out of your codebase, making it secure and configurable per environment.Security Best Practice: For production, use a secrets management service (AWS Secrets Manager, HashiCorp Vault, etc.) instead of .env files.
1.6 Docker Setup for PostgreSQL (Optional)
If you don't have PostgreSQL running locally, create a
docker-compose.yml
file:Run
docker-compose up -d
to start the database container in the background.2. Database Schema and User Model (Prisma)
We'll use Prisma to define our database schema and interact with the database.
2.1 Define Prisma Schema
Edit
prisma/schema.prisma
:Why this schema?
id
: Standard unique primary key.email
,password
: Essential for basic authentication.phone
: Needed for Sinch SMS OTP. Making it@unique
prevents multiple accounts using the same phone number for verification. Decide if it should be nullable or required based on your signup flow.createdAt
,updatedAt
: Standard practice for tracking record changes.2.2 Generate Prisma Client and Run Initial Migration
prisma/migrations
directory if it doesn't exist.prisma/migrations/20250420000000_init/migration.sql
).User
table).2.3 Create Prisma Plugin for Fastify
Create
src/plugins/prisma.ts
to make the Prisma client available throughout your Fastify application.Why this plugin?
fastify-plugin
to avoid encapsulation issues and makefastify.prisma
available globally within the Fastify instance.3. Implementing Core Functionality (Auth Service & Controller)
Now, let's build the core logic for user registration, login, and preparing for OTP.
3.1 Configuration Setup
Create
src/config/index.ts
to load environment variables safely.Why Zod for config? Ensures required variables are present and have the correct type at application startup, preventing runtime errors later.
3.2 Zod Schemas and Type Definitions
Create
src/modules/auth/auth.schema.ts
to define input shapes using Zod and centralize JWT payload types.Why Zod schemas? Provides runtime validation of request bodies and parameters, ensuring data integrity before it hits your service logic. Also enables type inference for request handlers. Centralizing JWT types improves consistency.
3.3 Password Hashing Utility
Create
src/utils/hash.ts
:Why bcrypt? The standard and secure way to hash passwords. Never store plain text passwords.
3.4 Auth Service Logic
Create
src/modules/auth/auth.service.ts
. This handles the core business logic, interacting with the database and hashing.Why this structure?
PrismaClient
makes the service testable (you can pass a mock client).http-errors
for standard, descriptive HTTP errors. Throws errors instead of returning complex objects.3.5 Auth Controller (Request/Response Handling)
Create
src/modules/auth/auth.controller.ts
. This connects HTTP requests to the service logic.Frequently Asked Questions About Sinch SMS OTP with Fastify
How does Sinch Verification API work with Fastify?
Sinch Verification API integrates with Fastify through the official
@sinch/verification
SDK. You create a Fastify plugin that initializes the Sinch client with your application credentials, then expose methods for initiating SMS OTP (sending verification codes) and verifying user-entered codes. The SDK handles communication with Sinch's platform, including SMS delivery, OTP generation, and verification status tracking.What are the Sinch SMS pricing and rate limits?
Sinch SMS OTP pricing varies by country and volume, typically ranging from $0.005 to $0.10 per verification attempt. Free trial accounts include limited verification credits for testing. Rate limits depend on your account tier but generally allow 1-5 verification attempts per phone number per hour to prevent abuse. Check your Sinch dashboard for specific pricing and implement exponential backoff for rate-limited requests.
How secure is SMS-based OTP compared to other 2FA methods?
SMS OTP provides moderate security but has known vulnerabilities including SIM swapping attacks, SMS interception, and phishing. Per OWASP guidelines, SMS 2FA should not be the sole protection for high-value applications containing PII or financial data. For stronger security, consider TOTP authenticator apps (Google Authenticator, Authy), hardware tokens (YubiKey), or WebAuthn/FIDO2. SMS OTP works well for moderate-security use cases like account recovery or secondary verification.
What Node.js version is required for Fastify v5?
Fastify v5 requires Node.js v20 or later. Node.js v18 reached end-of-life in March 2025 and no longer receives security updates. Current LTS versions are v20 (Maintenance LTS, Iron) and v22 (Active LTS, Jod). Always use actively supported Node.js versions in production to ensure security patches and optimal performance.
How do I test SMS OTP locally without sending real messages?
For local testing, use Sinch's verification callback simulation or implement a development mode that bypasses actual SMS sending. You can create a test endpoint that returns mock OTP codes, check if
NODE_ENV=development
and use hardcoded test codes (like "1234"), or use Sinch's test phone numbers if available in your region. Always validate production flows with real SMS to ensure carrier compatibility.What's the recommended JWT expiration time for OTP tokens?
For OTP-specific tokens (issued after password verification but before OTP entry), use short expiration times of 5-10 minutes. This limits the window for token theft while giving users enough time to receive and enter their SMS code. For standard access tokens (issued after successful OTP verification), OWASP recommends 15-30 minute idle timeouts. Implement refresh token rotation for longer sessions.
How do I handle Sinch API errors in production?
Implement comprehensive error handling for Sinch API responses including network failures, invalid phone formats (error code 40003), rate limiting (429), and verification failures. Log all Sinch interactions with correlation IDs for debugging, use circuit breakers to prevent cascade failures, and provide user-friendly error messages without exposing internal details. Monitor Sinch dashboard for service status and set up alerts for elevated error rates.
Can I use Sinch SMS OTP with other databases besides PostgreSQL?
Yes, Sinch SMS OTP integration is database-agnostic. While this guide uses PostgreSQL with Prisma, you can use any database supported by Prisma (MySQL, SQLite, MongoDB, SQL Server, CockroachDB) or other ORMs like TypeORM, Sequelize, or Mongoose. The core requirement is storing user phone numbers and authentication state. Adjust your Prisma schema datasource provider and connection string accordingly.
How do I implement retry logic for failed SMS deliveries?
Implement exponential backoff with maximum retry limits (typically 3 attempts) using libraries like
p-retry
or custom retry logic. Track verification attempts in your database to prevent abuse, add delays between retries (1s, 2s, 4s), and provide users with alternative delivery methods (voice call via Sinch FlashCall) after multiple failures. Log all retry attempts and failure reasons for debugging carrier-specific issues.What are E.164 phone format requirements for Sinch?
E.164 is the international phone number format required by Sinch:
+[country code][subscriber number]
with maximum 15 digits total and no spaces or special characters. Examples:+14155552671
(US),+442071838750
(UK),+61291234567
(Australia). Validate phone numbers using the regex/^\+?[1-9]\d{1,14}$/
and use libraries likelibphonenumber-js
for parsing and formatting user input before sending to Sinch.