VerifyHub is a production-ready, highly secure, and multi-tenant self-hosted API designed to centralize the generation, delivery, and validation of verification codes (OTPs - One-Time Passwords) across multiple applications. It natively supports email dispatch via SMTP and WhatsApp automated messaging through whatsapp-web.js (Puppeteer/Chromium).
With VerifyHub, you don't need to rebuild authentication flow logic, SMTP configuration, or WhatsApp Web session registry for every new service or project. It isolates tenant configurations (workspaces, credentials, templates, and keys) at the database and application levels.
- Framework: NestJS (v11) with Fastify for high-throughput HTTP performance.
- ORM & DB: Prisma (v6) with PostgreSQL (v18).
- Caching & Queueing: Redis (v8) as the distributed lock provider and state store, backed by BullMQ for asynchronous background message dispatch.
- WhatsApp Web Integration: whatsapp-web.js with automated Puppeteer/headless Chromium sessions.
- Security: AES-256-GCM encryption for stored SMTP credentials, SHA-256 with pepper hashing for API Keys, CORS, Helmet, rate limiting, and HMAC-SHA256 signatures for Webhook payloads.
- CI/CD & Docker: Multi-stage Docker build with system Chromium configuration, Docker Compose environment, and GitHub Actions workflow.
verifyhub-api/
βββ .github/
β βββ workflows/
β βββ ci.yml # Automated CI pipeline (lint, migrations, tests, docker check)
βββ docs/
β βββ deployment_guide.md # Detailed environment setup and backup practices
β βββ integration_guide.md # SDK initialization and HMAC validation examples
βββ examples/
β βββ nestjs-integration/ # Example external NestJS app consuming VerifyHub
β βββ springboot-integration/ # Example Spring Boot app consuming VerifyHub
βββ prisma/
β βββ migrations/ # PostgreSQL database migration scripts
β βββ schema.prisma # Prisma database model relationships
β βββ seed.ts # Database seeding script (optional)
βββ sdk/
β βββ verifyhub-sdk.ts # Lightweight TypeScript SDK wrapper for API clients
βββ src/
β βββ api-key/ # API Key management, hashing, scopes, and Guard
β βββ audit/ # Audit logging database service, page filter, and CSV exporter
β βββ auth/ # Dashboard admin auth (JWT, refresh cookies, RolesGuard)
β βββ challenge/ # OTP generator, validator, and public Verify Link controller
β βββ common/ # Configuration validation, filters, interceptors, Prisma/Redis services
β βββ connector/ # Delivery channels integration
β β βββ smtp/ # SMTP Mail client creation and validation
β β βββ whatsapp/ # whatsapp-web.js session manager, client registry, and lock
β βββ dashboard/ # Metric aggregation and daily database maintenance job
β βββ delivery/ # BullMQ message processors (delivery attempt tracker)
β βββ health/ # API Liveness/Readiness probes (Postgres & Redis health checks)
β βββ template/ # Rich text and HTML engine with version control and rollback
β βββ webhook/ # Webhook registry, HMAC signatures, and BullMQ retry handler
β βββ workspace/ # Workspace, Project, and Team member access control plane
β βββ main.ts # Server bootstrap (Fastify configuration, cookie parser, filters)
β βββ app.module.ts # Core dependency injection graph
βββ test/
β βββ app.e2e-spec.ts # Core Fastify controller liveness test
β βββ verifyhub.e2e-spec.ts # Comprehensive E2E integration test suite (26 cases)
βββ tsconfig.json # TypeScript compiler rules
βββ Dockerfile # Multi-stage production Docker container
βββ docker-compose.yml # Local orchestrator for PostgreSQL and Redis
βββ package.json # Project dependencies, metadata, and build scripts
Follow these steps to run VerifyHub API on your local machine or server.
Ensure you have the following installed:
- Node.js (v20 or higher)
- pnpm (preferred) or npm
- Docker and Docker Compose
- PostgreSQL Client (optional, for debugging)
Clone the codebase and run package installation:
git clone https://github.com/joanlavender/verifyhub-api.git
cd verifyhub-api
pnpm installCopy the template configuration file:
cp .env.example .envOpen .env and configure your keys. For local development, the defaults are configured to match the docker-compose.yml services:
JWT_ACCESS_SECRET/JWT_REFRESH_SECRET: Random hex strings (at least 32 characters) for signing JWT cookies.ENCRYPTION_MASTER_KEY: A secure string of 32-64 characters. VerifyHub hashes this key to derive a 256-bit AES key used to encrypt SMTP passwords.API_KEY_PEPPER: Random string used to salt API keys before hashing them to database.DATABASE_URL: DB connection string. Local default:postgresql://verifyhub_user:verifyhub_password@localhost:5435/verifyhub_db?schema=publicREDIS_URL: Cache connection string. Local default:redis://localhost:6375/0
Start the local PostgreSQL (port 5435) and Redis (port 6375) databases:
docker compose up -dVerify the containers are running:
docker compose psGenerate the Prisma client and deploy the tables to the Postgres database:
npx prisma migrate devThis command creates all tables defined in prisma/schema.prisma and applies the schema changes.
Run the NestJS application with hot reloading enabled:
pnpm run start:devThe server will boot up using Fastify and listen on http://localhost:3000. You can access the Swagger OpenAPI documentation interface at:
π http://localhost:3000/docs
Once your API is running, configure your workspace, connectors, and templates to start sending OTP codes.
The control plane is managed by human users. First, register a new administrator user on the dashboard:
- Register: Send a
POSTrequest to/v1/auth/registerwithemailandpassword. - Login: Send a
POSTrequest to/v1/auth/loginwith your credentials. The server returns user info and sets secure, HttpOnly, SameSite cookies containing the JWT access and refresh tokens. - Subsequent requests to the control plane are authenticated automatically via these cookies.
All configurations belong to a Workspace (representing a client, company, or tenant) and a Project (representing a specific application environment).
- Create Workspace: Send
POST /v1/workspaceswithname. Note the returnedworkspaceId. - Create Project: Send
POST /v1/workspaces/:workspaceId/projectswithnameandallowedDomains(e.g.,["mycompany.com"]). Note the returnedprojectId.
External systems interact with the /v1/challenges endpoint using Machine-to-Machine (M2M) API keys.
- Create API Key: Send a
POSTrequest to/v1/projects/:projectId/api-keyswith the query parameter?workspaceId=:workspaceId.- Body payload:
{ "name": "Production backend key", "scopes": ["challenge:write", "challenge:read"] }
- Body payload:
- Save the Key: Save the returned plain text key (e.g.,
vh_live_...). This key is only shown once; only its SHA-256 hash is saved to the database. - Authenticate your client calls by passing the header
x-api-key: vh_live_....
Connectors manage connection credentials for your delivery channels.
- Create SMTP Connector: Send a
POSTrequest to/v1/connectors/smtp?workspaceId=:workspaceId.- Body:
{ "host": "smtp.mailtrap.io", "port": 2525, "secure": false, "username": "my-user", "password": "my-password", "fromName": "VerifyHub Security", "fromEmail": "security@verifyhub.com" } - The password is encrypted in the database using AES-256-GCM.
- Body:
- Verify Connector: Send
POST /v1/connectors/smtp/:id/test?workspaceId=:workspaceId. Nodemailer checks credentials and writes success state (testedAt) or error code to database.
VerifyHub supports multiple concurrent WhatsApp sessions using headless Chromium.
- Create WhatsApp Session: Send a
POSTrequest to/v1/connectors/whatsapp?workspaceId=:workspaceIdwith a nickname:{ "name": "Support Phone" }.- VerifyHub spins up an isolated Chromium profile in the background. It locks initialization using Redis (
lock:wwebjs:connector:<id>) to prevent concurrency corruption.
- VerifyHub spins up an isolated Chromium profile in the background. It locks initialization using Redis (
- Get QR Code: Poll
GET /v1/connectors/whatsapp/:id/status?workspaceId=:workspaceId.- Once status is
QR_READY, scan the returned base64qrCodeusing your physical WhatsApp phone (Linked Devices).
- Once status is
- Connection State: The status will update to
AUTHENTICATED, and finally toREADY. Your session credentials are saved underwwebjs_sessions/for auto-reconnection. - Test Delivery: Send a
POSTrequest to/v1/connectors/whatsapp/:id/test-send?workspaceId=:workspaceIdwithto(e.g.+5491123456789) andmessageto verify messaging is operational.
Verification messages require a template.
- Create Template: Send
POST /v1/templates?workspaceId=:workspaceId.- Body:
{ "name": "login_otp", "type": "EMAIL", "subject": "Your Secure Verification Code", "bodyHtml": "<h3>Your code is: <strong>{{code}}</strong></h3>", "bodyText": "Your verification code is: {{code}}" }
- Body:
- Version Control: Every update to a template creates a new version (
TemplateVersion). - Rollback: Roll back to a previous template layout by sending
POST /v1/templates/:templateId/rollback?workspaceId=:workspaceIdwithversionNumber.
Once connectors and templates are ready, your external client apps can trigger and verify codes.
Send a request from your external server backend using your API Key:
- Endpoint:
POST /v1/challenges - Header:
x-api-key: vh_live_... - Payload:
{ "channel": "EMAIL", "purpose": "VERIFY_EMAIL", "destination": "user@example.com", "templateName": "login_otp", "locale": "es" } - Response:
{ "id": "challenge-uuid-here", "status": "QUEUED", "expiresAt": "2026-06-12T17:58:35.000Z", "signedUrl": "http://localhost:3000/v1/verify-link?token=..." }- Note: In
testordevelopmentenvironment modes, the response also includes asandboxCodefield containing the raw OTP code for automated testing pipelines. - Behind the scenes, the challenge is queued inside BullMQ. The
DeliveryProcessorfetches the job, fetches the required template, processes translations, and executes Nodemailer or whatsapp-web.js to dispatch the code.
- Note: In
When the user enters the code on your client frontend, submit it for validation:
- Endpoint:
POST /v1/challenges/:id/verify - Header:
x-api-key: vh_live_... - Payload:
{ "code": "123456" } - Response:
{ "verified": true, "message": "Challenge verified successfully." }- The code is validated by comparing SHA-256 hashes.
- If validation fails 5 times, the challenge is marked as
BLOCKED. - If a user requests a code resend before 60 seconds have elapsed, VerifyHub rejects the request with a
429 Too Many Requests(Anti-Flood Cooldown).
When creating a challenge, the API returns a signedUrl (Verify Link).
- The user clicks this link in their browser (or is redirected there).
- The endpoint
/v1/verify-link?token=...decrypts and verifies the signature and expiration timestamp. - If valid, it renders a modern, premium HTML/CSS dashboard page (supporting English
enor Spanishestranslations) displaying a stylized input form. - The user types the OTP, and visual micro-animations show the validation status. If correct, they are safely redirected back to the project's configured allowed domains.
Configure Webhooks to notify your backend servers of verification updates.
- Register Webhook Endpoint: Send
POST /v1/projects/:projectId/webhooks?workspaceId=:workspaceId.- Body:
{ "url": "https://api.myclient.com/webhook-receiver", "events": ["challenge.sent", "challenge.verified"] } - Returns a webhook
secret(e.g.whsec_...).
- Body:
- Receive Webhooks: When events fire, VerifyHub sends a
POSTrequest to your URL. - Verify HMAC Signature: To ensure authenticity, compute the HMAC-SHA256 signature on the raw body payload prepended with the header timestamp:
signature = HMAC_SHA256(secret, header_timestamp + '.' + raw_body)Compare this value to the received headerX-VerifyHub-Signature.
VerifyHub is ready for multi-tenant deployment as a service:
- QuotaGuard: Active NestJS Guard that checks hourly/daily usage metrics against configured project policies. It rejects requests when limits are exceeded or if abnormal abuse is detected.
- Audit Logs: Every system action creates a log in PostgreSQL. Access logs using
GET /v1/audit-logs?workspaceId=:workspaceIdwith filters for dates, actions, and projects. Export logs to CSV format viaGET /v1/audit-logs/export?workspaceId=:workspaceId. - Database Maintenance: A daily cron job runs inside the
CleanupProcessorto safely purge challenges and delivery logs older than 30 days, keeping the database optimized.
VerifyHub has a rigorous automated test suite covering all services, security guards, and channel mocks.
Run the end-to-end (E2E) integration test suite:
pnpm run test:e2eNote: Make sure your Redis and PostgreSQL Docker containers are running before starting the tests.
To check codebase compliance with ESLint rules and automatic formatting:
pnpm run lint
pnpm run formatFor production deployments, utilize the multi-stage Docker build:
# Builds production package, installs Chromium and font libraries for Puppeteer compatibility,
# and runs under a non-root dedicated 'node' user for security containment.Deploy using Docker:
docker build -t verifyhub-api .
docker run -d --name verifyhub -p 3000:3000 --env-file .env verifyhub-apiEnsure a persistent volume is mapped to WWEBJS_SESSION_PATH (e.g. ./wwebjs_sessions) so WhatsApp sessions survive container restarts.
This project was architected, developed, and tested under senior backend paradigms by:
Juan Santos Pimentel Lalangui (Alias: Joan Lavender)
This project is licensed under the MIT License.