Skip to content
Arslan Vuzmal Lone
PROJECT · MESSAGING · WHATSAPP ROUTING & HUMAN HANDOFF

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.

By Arslan Vuzmal Lone·Built July 2026·Updated August 2026·Open source
ThreadHarbor → routing decisionsPer message
→“Do you deliver on Sundays?”
No triggerReply step
⇢“Can I talk to someone?”
Explicit RequestTicket opened
⇢“This is unacceptable”
Sentiment: NegativeTicket opened
⊘Same message ID again
Already processedIgnored
Figure 1:How messages are routed. Sample data.

At a glance

FOR
Teams answering customers on WhatsApp Business
DOES
Verify · de-duplicate · mask PII · escalate · hand off
INPUT
Meta WhatsApp Cloud API webhooks (text, images, PDFs)
OUTPUT
Replies via the Graph API, or a support ticket for a person
BUILT WITH
Python 3.11, FastAPI, Redis, SQLite analytics, Docker
HUMAN IN LOOP
Escalated chats go to a ticket; the bot stops replying

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.

One WhatsApp message through ThreadHarborStep 1 of 4
Step 01

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.

How it runsSelect a step · arrow keys move
Webhook
Guard
Route
Respond
Step 01 · Webhook

Signature check

What happens

Verifies X-Hub-Signature-256 HMAC over the raw request body

Uses

FastAPI, hmac

If it fails

401 Invalid signature


Execution sequence

One item, traced through the system boundaries.

An upset customer is handed to a person
  1. Customer to Meta Cloud API: “This is unacceptable”
  2. Meta Cloud API to ThreadHarbor: POST webhook + signature
  3. ThreadHarbor to ThreadHarbor: verify · claim ID · triggers (Sentiment: Negative)
  4. ThreadHarbor to Ticket system: create_ticket(session, context)
  5. Ticket system replies to ThreadHarbor: ticket ID
  6. ThreadHarbor to Ticket system: later messages forwarded to the ticket

One item, start to finish

1. Message received

Meta posts a webhook. The signature matches, and the message ID has not been seen.

2. Masked

A card number in the text becomes [CARD] before anything else reads it.

3. Escalated

“Unacceptable” is on the negative keyword list, so the bot does not reply.

4. With a person

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.

Hand off to a personreason: Sentiment: Negative
masked: This is unacceptable, my card [CARD] was charged twice
Triggers, in order: asking for an agent or human; the same message twice in a row; a negative keyword (angry, unacceptable, scam, refund). After the reply step, two “I don’t know” answers in a row, or a refund tool call above $500, also hand off.

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.

System Architecture StackTop-down request path
System Architecture Stack
LayerComponentTech StackResponsibility
Layer 1: GatewayFastAPI webhookPython 3.11, FastAPISignature check, verification handshake, background processing
Layer 2: StateRedisKEYRedisMessage idempotency and conversation sessions
Layer 3: OrchestratorRouting engineKEYPythonTriggers, masking, reply step, fallback, handoff
Layer 4: OutputsWhatsApp, tickets, analyticsGraph API v21.0, handoff client, SQLite, Langfuse tracingSend replies, open tickets, record metrics
ThreadHarbor decision pathRed dashed line = fallback route
  • 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

DecisionInstead ofWhyWhere in the code
Official Cloud API webhooksUnofficial WhatsApp Web librariesSigned webhooks from Meta can be verified; scraped sessions cannot.src/api/routes/webhook.py, src/utils/whatsapp_signature.py
Claim each message ID in RedisProcessing every deliveryMeta 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 runsLetting the model decide when to hand offRequests 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 textMasking only in logsCard numbers, emails and phone numbers are replaced with placeholders at the start of processing.src/utils/pii_masker.py
Source: Read from github.com/arslanvuzmal/ThreadHarbor

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.

To be measured
Routing decisions on a fixed message set

Escalate vs reply counts for a committed list of sample messages, run through the trigger engine.

To be measured
Webhook handling time in demo mode

Median and p95 from signature check to routing decision, measured locally with a recorded script.

To be measured
Effect on a support team

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 ↗

bash
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/ -v
Versions: Python 3.11 · Docker Compose

Questions 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.

AI Workflow Audit · €390 · Credited toward build · or email arslanvuzmallone@gmail.com