ThreadHarbor: WhatsApp messages checked, masked and handed to a person when they should be
A WhatsApp Business webhook service that verifies every message, ignores repeats, masks card numbers and contact details, and opens a support ticket when a customer asks for a person, repeats themselves or is upset.
At a glance
On WhatsApp, an automated reply at the wrong moment is worse than a slow one.
Customers write to businesses on WhatsApp at any hour. Some questions can be answered automatically; others need a person straight away: an angry customer, someone asking for an agent, someone repeating the same message because the bot did not help.
ThreadHarbor is the plumbing around the reply: it makes sure each message is genuine and handled once, keeps card numbers and contact details out of anything downstream, and knows when to stop and hand the conversation to a person.
Verify
The X-Hub-Signature-256 header is checked against the raw body. A bad signature gets 401.
How it works
Each stage as it exists in the repository. Select a stage to see what it uses and what happens when it fails.
Signature check
Verifies X-Hub-Signature-256 HMAC over the raw request body
FastAPI, hmac
401 Invalid signature
Execution sequence
One item, traced through the system boundaries.
- Customer to Meta Cloud API: “This is unacceptable”
- Meta Cloud API to ThreadHarbor: POST webhook + signature
- ThreadHarbor to ThreadHarbor: verify · claim ID · triggers (Sentiment: Negative)
- ThreadHarbor to Ticket system: create_ticket(session, context)
- Ticket system replies to ThreadHarbor: ticket ID
- ThreadHarbor to Ticket system: later messages forwarded to the ticket
One item, start to finish
Meta posts a webhook. The signature matches, and the message ID has not been seen.
A card number in the text becomes [CARD] before anything else reads it.
“Unacceptable” is on the negative keyword list, so the bot does not reply.
A ticket opens with the conversation so far; further messages go straight to it.
When ThreadHarbor stops and calls a person
Type a customer message. The triggers and the masking are the repository’s own rules.
Before any model sees a WhatsApp message, ThreadHarbor checks whether a person should take over, and masks card numbers, emails and phone numbers. Type a message.
Logic copied from ThreadHarbor src/orchestrator/triggers.py and src/utils/pii_masker.py. Inputs are yours to change; nothing here calls a model or a server.
Architecture
The layers and the decision path, taken from the repository code.
| Layer | Component | Tech Stack | Responsibility |
|---|---|---|---|
| Layer 1: Gateway | FastAPI webhook | Python 3.11, FastAPI | Signature check, verification handshake, background processing |
| Layer 2: State | RedisKEY | Redis | Message idempotency and conversation sessions |
| Layer 3: Orchestrator | Routing engineKEY | Python | Triggers, masking, reply step, fallback, handoff |
| Layer 4: Outputs | WhatsApp, tickets, analytics | Graph API v21.0, handoff client, SQLite, Langfuse tracing | Send replies, open tickets, record metrics |
- Meta webhook goes to Signature: POST
- Signature goes to Seen before?: valid
- Seen before? goes to Triggers: new
- Triggers goes to Reply step: no trigger
- Triggers falls back to Ticket: escalate
Decisions and trade-offs
| Decision | Instead of | Why | Where in the code |
|---|---|---|---|
| Official Cloud API webhooks | Unofficial WhatsApp Web libraries | Signed webhooks from Meta can be verified; scraped sessions cannot. | src/api/routes/webhook.py, src/utils/whatsapp_signature.py |
| Claim each message ID in Redis | Processing every delivery | Meta can deliver the same webhook more than once; the one-hour claim stops double replies. | src/api/routes/webhook.py (check_message_idempotency) |
| Escalate before the model runs | Letting the model decide when to hand off | Requests for a person and angry messages are caught by simple rules before any reply is generated. | src/orchestrator/triggers.py (evaluate_pre_llm) |
| Mask before anything reads the text | Masking only in logs | Card numbers, emails and phone numbers are replaced with placeholders at the start of processing. | src/utils/pii_masker.py |
Known limits
The reply step is simulated
In this version the model call is a stand-in that returns fixed replies for test scenarios. The routing, masking, escalation and handoff around it are real; connecting a model and a knowledge base is the next step.
Keyword sentiment
Negative sentiment is a keyword list (angry, unacceptable, scam, refund), not a model. It is predictable but will miss politely furious customers.
Mock ticketing
The handoff client included is a Zendesk-style mock. A real help desk needs its own client class.
Results
Only numbers produced by a committed, repeatable script appear here as measured. Until then, each result says how it will be measured.
Escalate vs reply counts for a committed list of sample messages, run through the trigger engine.
Median and p95 from signature check to routing decision, measured locally with a recorded script.
Only measurable with a client: time to first human reply on escalated chats.
Run it yourself
From the README: install, configure .env, start the stack, run the tests. Full instructions ↗
git clone https://github.com/arslanvuzmal/ThreadHarbor.git
cd ThreadHarbor
pip install -e ".[dev]"
cp .env.example .env # set WHATSAPP_VERIFY_TOKEN and WHATSAPP_APP_SECRET
docker compose up -d --build
pytest tests/ -vQuestions people ask first
Do I need an official WhatsApp Business account?
Yes. ThreadHarbor receives webhooks from Meta’s Cloud API, which needs a verified business account and app secret.
When does a person take over?
When the customer asks for an agent or a human, sends the same message twice in a row, uses one of the negative keywords, or gets two “I don’t know” replies in a row. Refund requests above $500 are also escalated.
Does it answer questions with AI today?
Not yet. The reply step is simulated in this version; the system around it is built and tested so a model can be plugged in.
What happens to card numbers in messages?
They are replaced with [CARD] at the start of processing, along with emails and phone numbers.
Related reading & systems
Want ThreadHarbor working in your business?
Book a 20-minute scoping call. I will review your current systems, map where manual friction is costing you time, and give you an honest quote.