A production-oriented TypeScript service that conducts a guided Form TM-A trademark intake over WhatsApp, collects supporting media, presents a final review, generates a draft PDF, and returns the document to the user through the WhatsApp Cloud API.
The questionnaire is based on the fields represented in an Indian Trade Marks Form TM-A under the Trade Marks Act, 1999. Questions are declared in code and can be changed or localized without rewriting the conversation engine.
Important: This project generates a draft intake summary. It does not file a trademark application, replace the official form, or provide legal advice. A qualified professional should review all information before submission to the Trade Marks Registry.
- What the bot does
- Features
- Architecture
- Conversation flow
- Information collected
- Prerequisites
- Quick start with Docker
- Run without Docker
- Configure Meta WhatsApp
- Environment variables
- External PDF API contract
- HTTP endpoints
- Project structure
- Development and testing
- Security and privacy
- Production readiness
- Troubleshooting
- Customization
- Documentation
- Contributing and license
- Receives an inbound WhatsApp message through a signed Meta webhook.
- Displays a privacy notice and asks the user to reply
START. - Asks one TM-A question at a time and validates each answer.
- Conditionally asks for agent, prior-use, enterprise, trademark-image, and authorization details.
- Downloads images and documents from WhatsApp when the user uploads them.
- Shows a human-readable summary and lets the user confirm or edit the answers.
- Calls a configured PDF-generation API or uses the included PDFKit renderer.
- Uploads the resulting PDF to WhatsApp and sends it to the user as a document.
- Meta webhook subscription verification
- Constant-time HMAC-SHA256 validation of
X-Hub-Signature-256 - Durable BullMQ enqueue before webhook acknowledgement, with a separate worker process
- Declarative questionnaire with conditional branches
- Numbered choices and normalized free-text answers
- Email, phone, date, answer-length, message-type, and upload-size validation
- User commands:
BACK,SUMMARY,RESTART,CANCEL,EDIT, andCONFIRM - Redis-backed session persistence with configurable expiration
- WhatsApp message-ID deduplication to handle webhook retries safely
- WhatsApp image/document download and generated-PDF upload
- Configurable external PDF API with bearer-token support and timeouts
- Working built-in A4 PDF generator with page numbers and draft disclaimer
- Retry-safe PDF generation: failed jobs return to the review state
- Dependency-injected stores, messaging clients, and PDF providers for testing
- Dockerfile, Docker Compose, strict TypeScript, Vitest, and GitHub Actions CI
- No source-document personal data or credentials committed to the repository
┌──────────┐ messages/uploads ┌────────────────────────┐
│ End user │ ───────────────────────────> │ Meta WhatsApp Cloud API│
└──────────┘ └───────────┬────────────┘
^ │ signed webhook
│ generated PDF v
│ ┌──────────────────────┐
└─────────────────────────────────────│ Express webhook │
│ verification + HMAC │
└──────────┬───────────┘
│ durable enqueue
v
┌─────────┐
│ BullMQ │
└────┬────┘
│ worker pickup
v
┌──────────────────────┐
│ Conversation engine │
└───────┬────────┬─────┘
│ │
sessions/idempotency │ PDF request
v v
┌────────┐ ┌────────────────┐
│ Redis │ │ Local or API │
└────────┘ │ PDF provider │
└────────────────┘
The main abstractions are:
SessionStore: session persistence and inbound-message claims.MessagingClient: text delivery, media download, and PDF delivery.PdfProvider: creation of a PDF from normalized answers and uploaded files.ConversationService: channel-independent state machine and command handling.
See docs/DESIGN.md for the full component design, data model, reliability tradeoffs, API contract, scaling path, and threat considerations.
new user
│
v
consent ── START ──> collecting questions
│
├── BACK / SUMMARY
├── CANCEL ──> cancelled
└── last answer
│
v
reviewing
/ \
EDIT CONFIRM
│ │
v v
collecting generating
│
success│failure
│
completed/reviewing
Commands are case-insensitive:
| Command | Available state | Behavior |
|---|---|---|
START |
New/consent | Accepts the notice and begins intake. |
BACK |
Collecting | Returns to the previous applicable question. |
SUMMARY |
Collecting/reviewing | Shows answers collected so far. |
EDIT |
Reviewing | Replays applicable questions while retaining current answers. |
CONFIRM |
Reviewing | Generates and sends the PDF. |
CANCEL |
Collecting/reviewing | Cancels the current intake. |
RESTART |
Any | Creates a fresh intake session. |
The default questionnaire covers:
- application filing category;
- applicant name, address, country, jurisdiction, service address, mobile, and email;
- applicant nature and legal status;
- optional agent name, address, role, registration number, and POA;
- mark category, wording, description, image, and language;
- limitations or conditions on use;
- Nice Classification class and goods/services description;
- proposed-use or prior-use statement and first-use date;
- other important statements;
- verification name and date; and
- Startup/Small Enterprise supporting documentation when applicable.
- Node.js 20 or newer, or Docker with Docker Compose
- Redis 7 or a compatible managed Redis service
- A Meta developer app with the WhatsApp product enabled
- A WhatsApp Business phone-number ID
- A suitable WhatsApp Cloud API access token
- A public HTTPS URL for the webhook
git clone https://github.com/manishklach/whatsapp-tma-bot.git
cd whatsapp-tma-bot
cp .env.example .envEdit .env, then run:
docker compose up --buildConfirm the service is healthy:
curl http://localhost:3000/healthExpected response:
{"status":"ok"}Docker Compose starts the application and a Redis 7 instance with append-only persistence. The application is exposed on port 3000 by default.
Start Redis locally or configure a managed instance, then:
npm ci
cp .env.example .env
npm run devIn a second terminal, start the durable message worker:
npm run dev:workerFor a production-style local run:
npm run build
npm start
# In a separate process:
npm run worker-
Create or open a Meta developer app and add the WhatsApp product.
-
Obtain the app secret, access token, and WhatsApp phone-number ID.
-
Deploy or expose this service through a public HTTPS URL.
-
In the WhatsApp webhook configuration, set the callback URL to:
https://YOUR_HOST/webhooks/whatsapp -
Enter the exact value configured as
WHATSAPP_VERIFY_TOKEN. -
Subscribe the WhatsApp Business Account to the
messageswebhook field. -
Send a test message to the configured WhatsApp number.
The Graph API version is configurable so deployments can upgrade without source changes. See docs/DEPLOYMENT.md for operational setup and the production checklist.
| Variable | Required | Default | Purpose |
|---|---|---|---|
PORT |
No | 3000 |
HTTP listening port. |
NODE_ENV |
No | development |
Runtime mode: development, test, or production. |
WHATSAPP_VERIFY_TOKEN |
Yes | - | Secret value used during Meta webhook verification. |
WHATSAPP_APP_SECRET |
Yes | - | Meta app secret used for webhook HMAC verification. |
WHATSAPP_ACCESS_TOKEN |
Yes | - | Token used for Graph API messaging and media operations. |
WHATSAPP_PHONE_NUMBER_ID |
Yes | - | Sender phone-number ID in the WhatsApp Cloud API. |
WHATSAPP_GRAPH_API_VERSION |
No | v26.0 |
Version prefix used for Graph API requests. |
REDIS_URL |
No | redis://localhost:6379 |
Redis connection URL. |
SESSION_TTL_SECONDS |
No | 604800 |
Session retention period; default is seven days. |
PDF_API_URL |
No | empty | External PDF endpoint. Empty selects the local renderer. |
PDF_API_TOKEN |
No | empty | Optional bearer token for the external PDF endpoint. |
PDF_API_TIMEOUT_MS |
No | 30000 |
PDF request and download timeout. |
PDF_API_DOWNLOAD_ALLOWLIST |
No | empty | Comma-separated trusted parent domains or exact hosts for downloadUrl responses. Empty rejects every download URL while still allowing direct PDF responses. |
Never commit .env. The repository includes only .env.example placeholders.
When PDF_API_URL is configured, the bot sends an HTTP POST using multipart/form-data.
payload: JSON string containing all scalar and boolean answers.markImage: uploaded image part when supplied.enterpriseDocument: uploaded enterprise proof when supplied.poaDocument: uploaded authorization document when supplied.Authorization: Bearer <PDF_API_TOKEN>when a token is configured.
Conceptual payload:
{
"applicationFiledAs": "Small Enterprise",
"applicantName": "Example Applicant",
"hasAgent": true,
"markCategory": "DEVICE",
"tradeMark": "EXAMPLE",
"classNumber": "35",
"useStatement": "Proposed to be used"
}Return PDF bytes directly:
HTTP/1.1 200 OK
Content-Type: application/pdfOr return a download URL:
{
"downloadUrl": "https://files.example.com/generated/document.pdf"
}Download URLs must use HTTPS and their hostname must match PDF_API_DOWNLOAD_ALLOWLIST. An entry such as files.example.com permits that exact host and its subdomains; redirects are rejected. Leave the allowlist empty if the external provider always returns PDF bytes directly.
An API error or timeout returns the session to reviewing; the user can reply CONFIRM to retry without re-entering answers.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Liveness response without sensitive data. |
GET |
/webhooks/whatsapp |
Meta webhook challenge verification. |
POST |
/webhooks/whatsapp |
Signed inbound WhatsApp events. |
The webhook body is limited to 2 MB. User-uploaded media is downloaded separately through the Graph API and limited to 15 MB per item.
.
├── .github/workflows/ci.yml # Typecheck, tests, and build
├── docs/
│ ├── AI_DEPLOYMENT_RUNBOOK.md # End-to-end AI operator procedure
│ ├── DESIGN.md # Architecture and engineering decisions
│ └── DEPLOYMENT.md # Meta and production deployment guide
├── src/
│ ├── app.ts # Express routes and HMAC verification
│ ├── config.ts # Zod environment validation
│ ├── conversation.ts # Intake state machine
│ ├── index.ts # Dependency wiring and process lifecycle
│ ├── pdf.ts # Local and external PDF providers
│ ├── questions.ts # TM-A questions and validation
│ ├── queue.ts # BullMQ producer and worker wiring
│ ├── store.ts # Redis and in-memory stores
│ ├── types.ts # Shared interfaces and data types
│ ├── whatsapp.ts # Graph API client and webhook parser
│ └── worker.ts # Dedicated message-worker entry point
├── tests/
│ ├── conversation.test.ts
│ └── webhook.test.ts
├── docker-compose.yml
├── Dockerfile
└── package.json
Install exact locked dependencies:
npm ciAvailable scripts:
| Command | Purpose |
|---|---|
npm run dev |
Start the service with TypeScript watch mode. |
npm run dev:worker |
Start the message worker with TypeScript watch mode. |
npm run typecheck |
Run strict TypeScript checking without output. |
npm test |
Run the Vitest suite once. |
npm run test:watch |
Run tests interactively in watch mode. |
npm run build |
Compile production JavaScript into dist/. |
npm start |
Run the compiled service. |
npm run worker |
Run the compiled message worker. |
The automated suite covers webhook verification and signatures, new-session behavior, message deduplication, choice validation, binary session serialization, and readable PDF creation. Every push and pull request runs installation, type checking, tests, and the production build in GitHub Actions.
This application processes phone numbers, contact information, trademark material, and potentially sensitive identity/business documents. Before production use:
- provide a clear privacy notice and lawful basis for processing;
- use a session TTL appropriate for your jurisdiction and business process;
- enable Redis TLS, authentication, encryption at rest, and restricted network access;
- store secrets in a managed secret store and rotate Meta tokens;
- limit application and operator access using least privilege;
- scan uploaded documents for malware;
- use encrypted object storage instead of Redis for larger/longer-lived media;
- redact personal data and tokens from structured logs;
- restrict external PDF download URLs to trusted destinations;
- document deletion, backup, incident-response, and data-subject-request procedures; and
- obtain professional legal and security review before handling production filings.
Implemented controls include webhook HMAC verification, constant-time signature comparison, request-size limits, message deduplication, upload-size limits, environment-only secrets, non-root container execution, generic user-facing errors, and text-only PDF rendering of user input.
The repository is a complete working reference implementation. Higher-volume or strict-delivery deployments should additionally:
- scale HTTP producers and BullMQ workers independently;
- monitor queue depth, failed jobs, stalled jobs, retries, and processing latency;
- add exponential backoff and jitter for Graph/API
429and5xxresponses; - add distributed tracing, structured redacted logs, metrics, and alerts;
- add rate limits per sender and business number;
- use encrypted object storage with short-lived references for uploads;
- validate the external PDF service against an outbound-domain allowlist;
- add dead-letter handling and operator replay tools; and
- run end-to-end tests using Meta test numbers before each production release.
- Confirm the callback is public HTTPS and ends in
/webhooks/whatsapp. - Confirm
WHATSAPP_VERIFY_TOKENexactly matches the value entered in Meta. - Check that the service is running and
/healthreturns200.
- Confirm
WHATSAPP_APP_SECRETbelongs to the same Meta app sending events. - Ensure a proxy is not modifying the raw JSON body.
- Do not replace the raw-body middleware on the webhook route with parsed JSON middleware.
- Confirm the access token is valid and has the required WhatsApp permissions.
- Confirm
WHATSAPP_PHONE_NUMBER_IDis the sender number, not the displayed phone number. - Inspect service logs for Graph API status codes and error bodies.
- Check Redis connectivity and persistence.
- Increase
SESSION_TTL_SECONDSif the configured retention policy permits it. - Verify Redis eviction and memory policies in the hosting environment.
- Leave
PDF_API_URLempty to verify the built-in renderer first. - If using an external provider, confirm its multipart field names and response format.
- Check
PDF_API_TIMEOUT_MS, authentication, response content type, and download URL accessibility.
- Change questions, options, validation, and branches in
src/questions.ts. - Implement a different
PdfProviderfor another document-generation service. - Implement a different
SessionStorefor PostgreSQL, DynamoDB, or another database. - Implement
MessagingClientto reuse the conversation engine on another channel. - Add locale-specific prompt dictionaries while retaining stable field IDs.
- Replace the local renderer if an official filing-template integration becomes available.
- Detailed design: architecture, state, PDF contract, data retention, security, reliability, testing, and extension points.
- Deployment guide: prerequisites, local setup, Meta webhook configuration, provider selection, and production checklist.
- AI-assisted deployment runbook: an end-to-end, safety-conscious procedure designed to be handed directly to Gemini or another AI operator.
- Ready-to-paste Gemini prompt: a standalone prompt that instructs Gemini to follow the runbook, protect secrets, stop at human checkpoints, and verify the complete deployment.
- Changelog: version history and release details.
Issues and pull requests are welcome. Run npm run typecheck, npm test, and npm run build before opening a pull request.
Released under the MIT License.