Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SMS Identity Verification

A standalone Node.js + Express backend that verifies whether an SMS sender ID belongs to a legitimate registered entity, using the official TRAI SMS Header registry.

It has no frontend and no frontend dependencies — it is a self-contained service that can be run on its own port or mounted inside another Express app, which makes it easy to drop into the SMS scam-detection application later.


What it does

data/sms_headers.xlsx  ──parser──▶  database/headers.db   (SQLite, queried by the API)
                                 └▶  database/headers.json (portable snapshot)

POST /verify-header  { "sender": "VK-HDFCBK" }
        │
        ├─ validate the format ────────────▶ 400 if malformed (no DB lookup)
        ├─ normalise to the header ────────▶ "HDFCBK"
        └─ look it up in SQLite ───────────▶ { verified: true, entityName: "HDFC BANK LIMITED" }

Supported sender ID formats

The sender ID is validated before any database lookup. Four real-world shapes are accepted:

# Format Examples Normalises to
1 XX-HEADER VK-HDFCBK, AX-ICICIB HDFCBK, ICICIB
2 XX-HEADER-TYPE JD-HDFCBK-S, JD-HDFCBK-P HDFCBK
3 Bare header HDFCBK, GODREJHFC, HDFCBK-S HDFCBK, GODREJHFC, HDFCBK
4 Numeric short code 512, 123456 512, 123456

Where:

  • XX — the operator/circle access code: exactly two uppercase letters (VK, AX, VM, JD, …). Stripped before lookup.
  • HEADER — 1–9 uppercase letters and/or digits. TRAI headers are normally 6 characters, but the registry also contains 39 longer entries (AUTOVIT, MERUCAB, GODREJHFC, …) and 1–5 character numeric short codes, so the accepted range is deliberately wider than 6 — a 6-character cap would make those entities impossible to verify.
  • TYPE — one uppercase letter marking the traffic category: S service, P promotional, T transactional, G government. Stripped before lookup.

Surrounding whitespace is trimmed, so " VK-HDFCBK " is accepted.

Rejected as malformed (HTTP 400)

Input Why
"", " " Empty / whitespace only
hello, vk-hdfcbk Lowercase — sender IDs travel in uppercase
VK- Trailing hyphen, empty header
-HDFCBK Leading hyphen, missing access code
VK--HDFCBK Consecutive hyphens
VK-HDFC@1, +91VK-HDFCBK Illegal characters
VK HDFCBK, VK_HDFCBK Wrong separator
VKM-HDFCBK, V1-HDFCBK Invalid access code
VK-HDFCBK-SP, VK-HDFCBK-S-P Invalid category suffix
VK-HDFCBKTOOLONG Header longer than 9 characters
missing / non-string sender Not a usable sender ID

A well-formed but unregistered sender ID is not a 400 — it returns 200 with verified: false, because the format was fine and the registry simply had no match.

Ambiguous IDs

A two-group ID like AB-S can be read two ways: access code AB + header S, or bare header AB + category S. Both readings are tried against the registry and the one that is actually registered wins.


Project structure

sms-identity-verification/
├── config/
│   └── index.js                  # All paths, port and column-name settings
├── data/
│   └── sms_headers.xlsx          # Official TRAI SMS header export (input)
├── database/
│   ├── headers.db                # SQLite database  (generated by the parser)
│   └── headers.json              # JSON snapshot    (generated by the parser)
├── parser/
│   ├── excelParser.js            # Reads + cleans the Excel file
│   └── index.js                  # CLI runner: Excel -> SQLite + JSON
├── routes/
│   └── verifyRoutes.js           # HTTP layer: POST /verify-header, GET /health
├── services/
│   ├── database.js               # SQLite connection, schema and queries
│   └── verificationService.js    # Header extraction + verification logic
├── test/
│   └── smoke.test.js             # End-to-end smoke test (node:test)
├── app.js                        # Express app (middleware + routes, no listen)
├── server.js                     # Process entry point (starts the server)
├── index.js                      # Library entry point for in-process use
└── package.json

1. Installation

Requires Node.js 18 or newer (tested on Node 24).

npm install

Dependencies (all open source):

Package Purpose
express HTTP server and routing
exceljs Reading the .xlsx file
better-sqlite3 Fast synchronous SQLite driver
cors Cross-origin access for API callers
morgan Request logging

2. Running the parser

npm run parse

This reads data/sms_headers.xlsx, cleans the data (trims whitespace, skips empty rows, removes duplicate headers) and rebuilds both outputs from scratch:

  • database/headers.db
  • database/headers.json

Example output:

[parser] Reading .../data/sms_headers.xlsx ...
[parser] Sheet "List of Headers": 23192 data rows, 0 empty/incomplete skipped, 2 duplicates removed, 23190 unique headers kept.
[parser] Wrote 23190 rows to .../database/headers.db
[parser] Wrote 23190 records to .../database/headers.json
[parser] Done in 673 ms.

Updating the registry later

Replace data/sms_headers.xlsx with a newer TRAI export and run npm run parse again — nothing else needs to change. The parser matches columns by title (Header, Principal Entity Name) rather than by position, so a reordered export still works. Extra accepted column titles are listed in config/index.js.


3. Starting the server

npm start
[server] SMS Identity Verification API listening on http://localhost:3000
[server] Registry loaded: 23190 registered TRAI headers.

Use npm run dev for auto-restart on file changes. The port can be changed with the PORT environment variable.


API

POST /verify-header

Request:

{ "sender": "VK-HDFCBK" }

Success (200):

{
  "verified": true,
  "header": "HDFCBK",
  "entityName": "HDFC BANK LIMITED",
  "message": "Registered TRAI SMS Header"
}

Not registered (200):

{
  "verified": false,
  "header": "FAKEBK",
  "entityName": null,
  "message": "Header not found in TRAI registry"
}

Malformed sender ID (400) — see Supported sender ID formats. The registry is not queried in this case:

{
  "verified": false,
  "header": null,
  "entityName": null,
  "message": "Invalid sender ID format. Expected XX-HEADER, XX-HEADER-TYPE, a bare header, or a numeric short code"
}

Registry not built (503) — run npm run parse:

{
  "verified": false,
  "header": null,
  "entityName": null,
  "message": "SQLite database not found at ... Run \"npm run parse\" to build it from the Excel file."
}

GET /health

{
  "status": "ok",
  "registry": {
    "records": 23190,
    "source_file": "sms_headers.xlsx",
    "sheet_name": "List of Headers",
    "record_count": "23190",
    "parsed_at": "2026-08-07T17:14:22.780Z"
  }
}

4. Testing with Postman

  1. Start the server: npm start.

  2. Create a new request:

    • Method: POST
    • URL: http://localhost:3000/verify-header
  3. Open the Body tab → select raw → choose JSON from the dropdown. (Postman then sets Content-Type: application/json automatically; if you set headers manually, add it yourself — the request is rejected without it.)

  4. Paste the body:

    { "sender": "VK-HDFCBK" }
  5. Click Send. You should get the success response shown above.

Try these senders to exercise the different paths:

Sender Expected
VK-HDFCBK 200 verified: true, HDFC BANK LIMITED
AX-ICICIB 200 verified: true, ICICI BANK LIMITED
VM-SBIBNK 200 verified: true, STATE BANK OF INDIA
JD-HDFCBK-S 200 verified: true (category suffix stripped)
HDFCBK 200 verified: true (bare header)
512 200 verified: true, KARODIAL (short code)
123456 200 verified: true, Blue Jay Finlease Limited
VK-FAKEBK 200 verified: false, header not in registry
hello 400 malformed sender ID
VK- 400 malformed sender ID
-HDFCBK 400 malformed sender ID
VK-HDFC@1 400 malformed sender ID
VK--HDFCBK 400 malformed sender ID
(no body) 400 malformed sender ID

Or test from the command line

curl -X POST http://localhost:3000/verify-header -H "Content-Type: application/json" -d "{\"sender\":\"VK-HDFCBK\"}"

Automated smoke test

npm test

Two suites:

  • test/senderValidation.test.js — unit tests for format validation and header normalisation across every supported and rejected shape. Runs without the database.
  • test/smoke.test.js — end-to-end POST /verify-header round trips on a random port. Requires the parser to have been run first.

Integrating into the scam-detection app

The module is frontend-agnostic and can be consumed three ways:

1. As a separate service — run it on its own port and call POST /verify-header over HTTP.

2. Mounted into an existing Express app:

const identityApp = require('./sms-identity-verification/app');
hostApp.use('/identity', identityApp); // -> POST /identity/verify-header

3. Called directly in-process (no HTTP):

const { verifySender } = require('./sms-identity-verification');

const result = verifySender('VK-HDFCBK');
// { verified: true, header: 'HDFCBK', entityName: 'HDFC BANK LIMITED',
//   message: 'Registered TRAI SMS Header', validFormat: true }

verifySender() returns an extra validFormat flag that the HTTP layer uses to choose 400 vs 200 and strips from the response body; in-process callers can use it to tell "malformed sender ID" apart from "not in the registry".

isValidSenderId(), extractHeader() and runParser() are exported too, so the host app can validate or normalise a sender ID on its own, or refresh the registry programmatically instead of shelling out to the CLI.


Notes

  • Sender ID validation is case-sensitive — lowercase input is treated as malformed, since sender IDs are transmitted in uppercase. The registry lookup itself is case-insensitive (the header column uses COLLATE NOCASE). To accept lowercase, uppercase the value in normaliseSender() in services/verificationService.js.
  • One registry entry (HP POL) contains a space and therefore cannot be matched, because spaces are not legal in a sender ID.
  • database/headers.db and database/headers.json are generated artefacts and are git-ignored — run npm run parse after cloning.
  • Verification confirms that a header is registered with TRAI; it does not by itself prove a message is safe. It is one signal for the scam-detection pipeline, not a verdict.

About

sms-identity-verifier

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages