{"slug": "building-a-chat-based-sales-bot-that-doesn-t-drop-messages-during-flash-sales", "title": "Building a Chat-Based Sales Bot That Doesn't Drop Messages During Flash Sales", "summary": "SellerVai, a platform providing automated sales assistants for WhatsApp, Facebook Messenger, and Telegram, has addressed the challenge of message loss during flash sales by decoupling webhook ingestion from processing. The system uses a queue (BullMQ with Redis) and message fingerprinting to ensure reliable delivery and prevent duplicate processing, even under high load.", "body_md": "Originally published at[parvejshah.com/blog/conversational-commerce-webhook-architecture]by[Parvej Shah].\n\nIn Bangladesh and much of South and Southeast Asia, e-commerce doesn't look like what a Silicon Valley product manager pictures. Buyers don't browse product catalogs, add items to carts, and check out with saved payment methods. They send a message on Facebook or WhatsApp, ask if an item is in stock, negotiate slightly, confirm their address, and pay by mobile banking transfer. The entire purchase funnel is a conversation.\n\n**SellerVai** is a platform built for exactly this reality: a 24/7 automated sales assistant that handles order inquiries, processes orders in Bengali and Banglish, and filters fake Cash-on-Delivery (COD) requests across WhatsApp Business API, Facebook Messenger, and Telegram.\n\nThe core engineering challenge wasn't the AI. It was the plumbing.\n\nEvery message sent to a business on WhatsApp or Facebook triggers an HTTP POST from Meta's servers to your registered webhook URL. The contract is simple: respond with 200 OK within a few seconds, or Meta assumes delivery failed and retries.\n\nWhen your webhook handler needs to classify intent, query a product database, check inventory, generate a personalized response, and sometimes initiate a payment collection flow — none of which can happen in a few seconds — you have a problem. The naive solution of doing all that work synchronously inside the webhook handler means you're constantly racing against the timeout, and you lose that race regularly during any period of elevated load.\n\nThe retry behavior makes it worse. When Meta doesn't get its 200 OK, it retries the same message. Now you have the same message being processed twice, potentially resulting in the same customer getting two replies, the same order being created twice, or two inventory decrements for a single purchase.\n\nThe solution is to treat the webhook endpoint as nothing more than an authenticated message receiver. Its only responsibility is to verify the signature and acknowledge delivery. All actual processing happens asynchronously.\n\n```\n// Webhook ingestion handler — responds in < 15ms\nexport async function POST(req: Request) {\n  const rawBody = await req.text();\n  const signature = req.headers.get(\"x-hub-signature-256\") ?? \"\";\n\n  if (!verifyMetaSignature(rawBody, signature, META_APP_SECRET)) {\n    return new Response(\"Forbidden\", { status: 403 });\n  }\n\n  const payload = JSON.parse(rawBody) as MetaWebhookPayload;\n\n  await messageQueue.add(\"process-incoming\", {\n    channel: \"whatsapp\",\n    rawPayload: payload,\n    receivedAt: Date.now(),\n  });\n\n  return new Response(\"OK\", { status: 200 });\n}\n```\n\nThe queue (BullMQ backed by Redis) holds the message until a worker picks it up. The webhook handler has already returned 200 OK to Meta and is completely done. The actual work — intent classification, inventory lookup, response generation — happens in worker processes with no timeout pressure.\n\nWorkers can't blindly process everything in the queue. If Meta retried a message three times before getting its 200 OK, there are three copies of that message in the queue.\n\nEvery message gets fingerprinted before processing. The fingerprint is derived from the channel, the sender ID, and the platform's native message ID. The fingerprint goes into Redis with a 5-minute TTL using a SET NX operation — set only if not exists. If the key already exists, that message has been processed recently and the worker skips it.\n\n```\nfunction buildMessageFingerprint(\n  channel: \"whatsapp\" | \"messenger\" | \"telegram\",\n  senderId: string,\n  messageId: string\n): string {\n  return crypto\n    .createHash(\"sha256\")\n    .update(`${channel}:${senderId}:${messageId}`)\n    .digest(\"hex\");\n}\n\nasync function processMessage(event: IncomingMessageEvent): Promise<void> {\n  const fingerprint = buildMessageFingerprint(\n    event.channel,\n    event.senderId,\n    event.messageId\n  );\n\n  const acquired = await redis.set(\n    `processed:${fingerprint}`,\n    \"1\",\n    \"NX\",\n    \"EX\",\n    300\n  );\n\n  if (!acquired) return; // Duplicate — already processed or in progress\n\n  await runConversationTurn(event);\n}\n```\n\nCustomer messages in social commerce are colloquial and informal. A real message looks like:\n\n\"vai ei sneaker ta ki size 42 ache? cash on delivery hobe? dhaka te delivery koto din lagbe?\"\n\nTranslation: *\"bro is this sneaker available in size 42? can I pay cash on delivery? how many days will delivery take to Dhaka?\"*\n\nThere are three distinct questions packed into one casual message, written in a mix of Bengali script words and Bengali-language words written in Roman characters.\n\nWe use a two-tier parsing approach. A fast regex and keyword engine handles structured data extraction: phone numbers, size numbers, city names, specific product codes. This runs in under 2ms. An LLM classifier handles intent categorization where casual phrasing and code-switching require genuine language understanding.\n\nThe real stress test came during a promotional campaign. Traffic spiked to roughly 15 times the baseline over a two-hour window. Because the ingestion layer is stateless and the queue absorbs the burst, the webhook endpoints stayed responsive. Workers processed the queue backlog over the following 20 minutes. Every message was processed. No duplicates were sent.\n\nThe architecture didn't require any changes for this scenario because it was designed with this scenario in mind from the start. Most reliability problems in messaging systems aren't hard to solve — they just require thinking through the failure modes before you're in them.\n\n*Parvej Shah is a Lead Full-Stack Web Developer & Platform Architect based in Dhaka, Bangladesh. Explore full architecture case studies and production code at parvejshah.com.*", "url": "https://wpnews.pro/news/building-a-chat-based-sales-bot-that-doesn-t-drop-messages-during-flash-sales", "canonical_source": "https://dev.to/parvejshah/building-a-chat-based-sales-bot-that-doesnt-drop-messages-during-flash-sales-2i77", "published_at": "2026-08-26 20:31:58+00:00", "updated_at": "2026-08-26 20:50:12.853119+00:00", "lang": "en", "topics": ["ai-agents", "ai-products", "developer-tools", "mlops"], "entities": ["SellerVai", "Meta", "WhatsApp", "Facebook Messenger", "Telegram", "BullMQ", "Redis", "Parvej Shah"], "alternates": {"html": "https://wpnews.pro/news/building-a-chat-based-sales-bot-that-doesn-t-drop-messages-during-flash-sales", "markdown": "https://wpnews.pro/news/building-a-chat-based-sales-bot-that-doesn-t-drop-messages-during-flash-sales.md", "text": "https://wpnews.pro/news/building-a-chat-based-sales-bot-that-doesn-t-drop-messages-during-flash-sales.txt", "jsonld": "https://wpnews.pro/news/building-a-chat-based-sales-bot-that-doesn-t-drop-messages-during-flash-sales.jsonld"}}