Dentolize · WhatsApp Conversation Race Fix Walkthrough
On this pageThe problem, in one sentenceWhy it happensWhat this PR changesWho should read what

Overview

This PR (#422mo/fix_conversation_upsert_race) fixes a bug in the Official WhatsApp integration where the first message from a new contact could be silently lost if it arrived at almost the same instant as another message from the same contact.

It touches one file: packages/whatsapp-official/src/services/online-conversation.service.ts. There is no schema change, no new UI, and no new user-facing setting — this is a backend reliability fix.

The problem, in one sentence

When two WhatsApp messages from a brand-new contact arrive close enough together, the code that is supposed to "find or create their conversation" can try to create the same conversation row twice. The database only allows one to win; the other used to throw an error that was silently swallowed, and with it, the customer's message and the bot's reply both disappeared.

Why it happens

Dentolize wants a single atomic "find-this-conversation-or-create-it" database operation (an upsert) that also attaches a new incoming message to whichever conversation it lands on. Prisma (the database library) cannot compile that in one step when the write includes nested creates like "and also create this message row" — so the code has to fall back to a read, then a plain write.

That two-step gap is where the race lives: two webhook deliveries for the same contact can both run the "read" half, both see nothing, and both try to run the "create" half. The database's unique constraint on (companyId, phone) and (companyId, bsuid) — see glossary — catches the second write and rejects it. Before this PR, that rejection propagated all the way out to the webhook handler's outer catch, which only logs the error to the console. The HTTP response to Meta had already been sent as 200 OK before any of this ran, so Meta never retries and the message is gone for good.

What this PR changes

Instead of letting the second, losing write fail outright, createOrUpdateOnlineConversation now catches that specific database error (Prisma code P2002) and lands the message on whichever conversation row actually exists — the one that won the race, or the one that already owned the clashing phone number / anonymous contact ID. See The race condition fix for the full technical walkthrough, and the glossary for the WhatsApp-specific terms (bsuid, upsert, unique constraint) used throughout this site.

Who should read what

  • Support / QA / training teams: the customer-visible symptom this fixes was

"message shows as delivered on WhatsApp but never appears in the Dentolize inbox, and the bot never replies" for a contact's very first message. See For Support and For Quality.

exact recovery logic and its remaining limitation (it does not merge the two conversation rows it discovers — that is called out as a separate job in the code's own comments).

  • Marketing / Sales / Stakeholders: this is a behind-the-scenes reliability

fix, not a feature to demo. See those pages for how (and whether) to talk about it externally.