DEV Community

Ujjwal Dubey
Ujjwal Dubey

Posted on

Modelling WhatsApp Conversations as a State Machine So Your CRM Pipeline Stays Honest

A common failure in WhatsApp-to-CRM builds: the CRM pipeline says "Qualified" while the chat has been silent for a long time. The pipeline and the conversation drift apart because nothing connects their states.

The fix I keep coming back to in WhatsApp CRM automation projects for small teams is to model the conversation as an explicit state machine, and let only state transitions touch the pipeline.

The states

from enum import Enum

class Conv(Enum):
    NEW = "new"                    # first inbound message
    AWAITING_US = "awaiting_us"    # customer spoke last
    AWAITING_THEM = "awaiting_them"  # we spoke last
    NEEDS_HUMAN = "needs_human"    # low confidence, price, complaint
    QUOTED = "quoted"              # approved quote sent
    WON = "won"
    LOST = "lost"
    OPTED_OUT = "opted_out"        # terminal for automated messages
Enter fullscreen mode Exit fullscreen mode

The allowed transitions

ALLOWED = {
    Conv.NEW: {Conv.AWAITING_US, Conv.NEEDS_HUMAN, Conv.OPTED_OUT},
    Conv.AWAITING_US: {Conv.AWAITING_THEM, Conv.NEEDS_HUMAN, Conv.OPTED_OUT},
    Conv.AWAITING_THEM: {Conv.AWAITING_US, Conv.LOST, Conv.OPTED_OUT},
    Conv.NEEDS_HUMAN: {Conv.AWAITING_THEM, Conv.QUOTED, Conv.LOST, Conv.OPTED_OUT},
    Conv.QUOTED: {Conv.AWAITING_US, Conv.WON, Conv.LOST, Conv.OPTED_OUT},
}

def transition(conv, new_state, actor):
    if new_state not in ALLOWED.get(conv.state, set()):
        raise ValueError(f"{conv.state} -> {new_state} not allowed")
    if new_state in (Conv.QUOTED, Conv.WON) and actor != "human":
        raise PermissionError("quotes and wins are confirmed by a person")
    conv.state = new_state
    sync_pipeline(conv)   # the ONLY place the CRM stage is written
Enter fullscreen mode Exit fullscreen mode

Why this helps

One writer. Only sync_pipeline writes the CRM stage, so you never have the bot, a Zap and a salesperson fighting over the same field.

Explicit human gates. Quotes and wins can only be set by a person. The AI can draft a quote; it cannot mark one sent.

Opt-out is terminal. Once OPTED_OUT, no automated template goes out, whatever any other rule says.

Silence becomes data. A scheduled job moves AWAITING_THEM to LOST after a quiet period you choose, and that shows up in the pipeline instead of rotting there.

Mapping to the CRM

Keep the mapping in config, because every CRM names stages differently:

pipeline_map:
  needs_human: "Contacted"
  quoted: "Proposal sent"
  won: "Closed won"
  lost: "Closed lost"
Enter fullscreen mode Exit fullscreen mode

States the CRM does not care about (awaiting_us, awaiting_them) live only in your automation database, and drive reminders to staff instead.

Before building any of this, map the real workflow with the people who do it. This list for mapping who owns each handoff is a good start.

We integrate with all major CRMs (for example HubSpot, Zoho, Salesforce, Pipedrive and Freshsales, plus custom CRMs or Google Sheets setups) and work with all major WhatsApp Business providers and the official WhatsApp Business API (for example Meta Cloud API, Twilio, Gupshup, Interakt and WATI).

Disclosure: I co-founded NxFlowAI, an AI automation agency in Mumbai. Code is illustrative and free to adapt.

Top comments (0)