ShopTap's premise is simple: a customer messages a shop on WhatsApp, browses products, places an order, and pays — all without leaving the conversation. No app to download, no website to navigate. Just WhatsApp, which they already use every day.
The problem is that WhatsApp has no native commerce API. There's no product catalogue object, no cart state, no payment integration. Building ShopTap meant building all of that on top of a messaging protocol that wasn't designed for it.
The architecture problem
A WhatsApp conversation is stateless from the API's perspective. You receive messages; you send messages. There's no concept of a session, a cart, or a transaction. To build commerce on top of this, you need a state layer that lives outside WhatsApp and tracks where each customer is in their shopping journey.
We built a conversation state machine that maps phone numbers to sessions. Each session has a state — browsing, product selected, quantity confirmed, payment pending, order complete — and each incoming message transitions the session based on the current state and the message content.
The state machine is the core of ShopTap. Every message from a customer hits the same webhook, gets looked up in the session store, and is processed according to the current state. This means the system can handle thousands of simultaneous conversations, each at a different point in the purchase flow.
Product catalogue and inventory
Shop owners manage their catalogue through a simple dashboard — product name, price, stock level, images. On the customer side, products are presented as numbered lists in WhatsApp messages. "Reply 1 for [product], 2 for [product]..." isn't as elegant as a storefront, but it works reliably on every device, with every WhatsApp client, including very old ones.
The constraint here was a feature: by limiting the interface to numbered choices, we avoided the reliability problems that come with interactive buttons and list messages (which have inconsistent support across different WhatsApp clients and operating systems).
Payments
We integrated mobile money (MoMo) for the Ghanaian market and Stripe for card payments. The payment flow is triggered by the state machine when the customer confirms their order: generate a payment link, send it in the conversation, wait for the webhook confirmation from the payment provider, then transition the session to "order complete" and send the confirmation message.
The tricky part: payment links expire. If a customer takes too long, the link is invalid. We built a payment timeout handler that detects stale sessions in "payment pending" state and sends a fresh link with an explanation. This reduced abandoned orders significantly in early testing.
Testing at scale
The state machine approach made testing much more tractable than it would have been with a more ad-hoc implementation. Every state transition is a discrete, testable unit. We wrote tests for each transition type — valid input, invalid input, edge cases like empty messages or emoji-only responses — before writing the production handlers.
Production reliability has been high. The main failure mode in the first month was upstream: WhatsApp API webhook delivery delays during high-traffic periods. We added a message deduplication layer after seeing duplicate order confirmations caused by delayed webhook retries.
What it taught us
Building constrained products is clarifying. WhatsApp's limitations forced decisions that turned out to be correct: numbered menus over rich interactive components, simple state machine over complex session logic, mobile money first over card-only.
The constraint that felt most limiting — no native commerce — turned out to be an advantage. Because we built the entire commerce layer ourselves, we could optimise it for the specific market and use case. The result is a system that works better for the target customer than a generic commerce platform integration would have.