How a WhatsApp Message Becomes Work
In DeskcommCRM, the important lifecycle begins before an AI agent answers: a WhatsApp message must become a trustworthy identity, a conversation, and—when appropriate—a durable business demand. This chapter follows that conversion so the reader can distinguish a contact from a conversation, a conversation from a lead, and a duplicate or unsupported event from a new piece of work. Those distinctions are what allow the rest of the system to act without losing the customer in the handoff between channel and CRM.
The starting point is an awkward but common message: someone writes to a business for the first time, WhatsApp supplies an identifier that may be incomplete, and the channel may deliver the same event more than once. DeskcommCRM does not solve that uncertainty by asking the language model to improvise. It creates a sequence of guarded transitions, each with a durable record and an explicit alternative when the expected information is missing.
The inbound boundary preserves uncertainty instead of hiding it
The WAHA integration presents a narrow public boundary. In the intended per-session topology, the incoming request contains a session-specific path token. The route uses it to find the channel session and, through that session, the organization to which the event belongs. It does not trust a tenant identifier supplied by a message body. That distinction matters in a multi-tenant system: the message is evidence about a customer, not authority to choose which customer organization receives it.
The route reads the raw request body before it is turned into a JavaScript object. It first checks the minimum envelope needed to identify the session and message, then records the raw webhook event, and only afterward applies the fuller payload contract. This two-stage validation looks less strict than rejecting everything immediately, but it preserves the evidence needed when WAHA changes its payload shape. A malformed or newly shaped event can be inspected and replayed instead of disappearing behind a parser exception.
Authentication has a deliberately visible trade-off. If a signature is present and does not match, the request is rejected. An installation can also set WAHA_WEBHOOK_REQUIRE_SIGNATURE to demand a valid HMAC signature. The default remains compatible with WAHA Core deployments that do not sign events: an unsigned event may be accepted, but the stored record says that its signature was not verified. The global webhook route is blocked at the public Caddy edge in the standard stack, while a tokenized per-session route is used when WAHA is hosted separately. “Supports HMAC” and “requires HMAC everywhere” are therefore different statements.
After the boundary accepts the event, the shared ingest layer interprets the chat identity. A normal WhatsApp address such as a phone-based chat can be converted into a canonical phone value. A newer LID address is opaque in its main identifier, but the payload may contain an alternate phone address; the ingest accepts that alternate only when it has the expected phone-shaped form. The system prefers a missing phone to a wrong phone. A wrong number is not merely incomplete data: it could send a future message to another person.
Groups take another path. A group message can be retained as channel history, but it does not represent one individual customer demand and therefore does not create the same CRM binding. An unknown chat suffix is different from a group: skipping a group is a product decision, while failing to recognize a new address format is an anomaly worth counting. DeskcommCRM records that distinction so a future change in WhatsApp’s identifiers can become an operational alert rather than a silent loss.
The ingest then resolves the contact and conversation through atomic database operations. This is more than a performance choice. WAHA can emit overlapping event forms for the same message, so a check-then-insert sequence can race with itself: two workers both see no contact and create two records. The current path uses database functions and uniqueness constraints to make the resolution one operation. The message carries the organization, conversation, channel session, external identifier, type, direction, and channel timestamp. The external identifier is the anchor for idempotency; when the same event arrives again, the duplicate is treated as a successful no-op rather than as a new customer action.
There is an important difference between a duplicate and a malformed event. A duplicate has enough identity to prove that the database already accepted the message, so acknowledging it is safe. A malformed event may indicate that the upstream contract has changed, so the system preserves the accepted raw webhook, records the validation problem, and returns an error that is meaningful for the sender. The raw record is not a substitute for the normalized message. It is the evidence that lets an operator compare what the channel actually sent with what the ingest layer understood. Headers that could contain session credentials are excluded from the stored diagnostic context, while the event type, external identifier, signature result, and processing state remain available for investigation.
The same discipline protects ordering. A provider timestamp describes when the message happened in WhatsApp; database insertion time describes when DeskcommCRM managed to receive it. Those moments can differ when the network is delayed or when two delivery forms arrive together. The normalized message retains the channel timestamp so the conversation can be read in the order the customer experienced it. A duplicate event does not add a second activity or wake a second downstream turn, and a message sent by another linked device can be represented as an outbound event without being mistaken for a message generated by the CRM. Identity, chronology, and idempotency therefore reinforce one another: the system can tell who the conversation belongs to, which message is new, and which apparent repetition is only transport noise.
The sequence ends with acknowledgment, not with an AI response. That is intentional. The channel request records what arrived; downstream workers decide what work follows. Keeping those responsibilities separate prevents a slow provider or a broken automation from forcing WhatsApp to resend a message that was already stored.
The conversation enters the funnel as a demand
A contact is the canonical identity of a person within one organization. A conversation is the interaction tied to a channel session. A lead is the business demand that can occupy a pipeline stage. The same contact can have several conversations and, over time, several demands. crm_lead_activities provides the timeline that connects those records to events such as inbound messages, state changes, and automated actions; links can connect a demand to an order, appointment, or conversation.
The distinction changes what “received” means. A message that only creates a contact may be searchable, but it is not necessarily visible to the mechanisms that detect stale opportunities or schedule follow-up. DeskcommCRM’s current post-ingest path therefore tries to ensure that an eligible conversation has a lead. When the contact is not blocked and has no open lead, the system creates one in the organization’s default pipeline and chooses the earliest active stage that is not already marked as won or lost. The operation is deterministic: it does not wait for the AI to decide whether remembering the person is worthwhile.
The default pipeline is data, not a hard-coded route hidden in the message handler. An organization can configure its own pipelines, and each pipeline can use vocabulary appropriate to the business. The underlying status might describe a won demand, while the visible label says “Paid” for a store, “Booked” for a clinic, or “Closed” for another operation. The order of stages supplies the entry point; the system does not need a second is_entry flag that could disagree with the order people see on the board.
This is also why the lead is created once per open demand rather than once per message. Suppose a customer asks about delivery, then asks for a tracking update, then confirms receipt. Those are several messages but one active commercial or service relationship. Creating a card for each message would fill the funnel with communication noise. Keeping one open lead lets the conversation and its activities accumulate against a stable business object. When that demand becomes won or lost and the person later returns with a new need, a new open demand can be created without erasing the earlier history.
The negative branches are as important as the successful one. A blocked contact does not become a new opportunity; creating a card for someone who opted out would turn a privacy decision into sales work. An existing open lead produces an explicit “already exists” outcome. An organization without a default pipeline, or with no usable non-terminal stage, produces a configuration outcome. The message can still remain in the channel record even when the CRM cannot create the demand. A failed write is not disguised as a deliberate skip, because the two conditions require different remedies.
Ambiguous identity is handled with the same restraint. Deterministic matching can use identifiers such as phone, email, or protected CPF, but an input that points to more than one active contact is sent to a manager-oriented merge queue rather than being silently merged. The cost of asking for review is visible work. The cost of attaching a conversation to the wrong person is corrupted history, incorrect follow-up, and possibly a message sent to the wrong recipient.
A merge is consequently a business operation, not a cleanup query. The merge logic gives one contact priority according to explicit evidence and redirects linked leads, activities, conversations, and orders, while preserving a tombstone for the records that lost. The operation is auditable and designed to be irreversible for personal data. This matters to the message lifecycle because every later decision—whether to send, follow up, retrieve knowledge, or satisfy a data request—depends on the system not confusing two people. A contact record is the anchor for continuity, so uncertainty about that anchor must become a queue for judgment rather than an invisible guess.
Configuration decides what the agent may move
Creating a lead in the entry stage does not mean giving every agent permission to move every card. DeskcommCRM treats the funnel as both vocabulary and scope. The agent configuration can name the pipelines in which it may write. An empty scope means no pipeline, not all pipelines. A write operation that is not classified by the scope layer is rejected rather than released by omission. If the system cannot resolve which pipeline a target belongs to, it reports unavailable information instead of translating a database failure into a false permission decision.
Stage mapping handles a related problem. The agent may reason in a small, stable set of operational states—new, contacted, qualifying, qualified, negotiating, won, or lost—while a tenant’s visible pipeline may use different names and have additional stages. The mapping layer translates between them and checks that a stage is not assigned to two agent states or that a winning state is not mapped to an ordinary stage. A missing mapping is surfaced next to the configuration that needs attention. It is not silently guessed from a similar word.
The result is a precise chain:
message received → identity resolved → conversation stored → open demand created or explicitly skipped → timeline activity emitted → downstream work awakened.
Each transition preserves a different kind of truth. The channel record says what was delivered. The contact says who the system can identify. The conversation says where the interaction belongs. The lead says what the organization is trying to resolve or sell. The timeline says what changed and why. The pipeline configuration says what the next visible state means.
That chain is the first practical expression of DeskcommCRM’s “living system” idea. A living system is not one that refuses to skip anything. It is one that distinguishes a valid skip from a failure, keeps the demand in a place a person or worker can find, and gives later components an authoritative object to act on. Once a message has become a durable demand, the problem is no longer how to create another record. It is how independent responsibilities—AI, automation, analytics, and compliance—can react to the same fact without making the original ingest request perform all of them synchronously.