WhatsApp Catalog and Product Messages
A WhatsApp product message renders an item from your Meta Commerce Manager catalog as an interactive bubble — image, name, price, and a tappable product card — and a multi-product message presents several SKUs in one list the customer can browse in-thread. Both give you a commerce-native message type Meta charges as a service conversation when the recipient is inside the 24-hour window, instead of burning a marketing-template conversation. This guide covers the catalog send surface: which message type to pick, the three send endpoints, how catalog ownership is bound to your WABAs, the dedicated rate-limit bucket, worked payloads and the errors you will actually see, and how to keep a feed-synced catalog in shape. It closes with the conversational commerce checkout narrative — the layer that turns the inbound product message and its resulting order into a paid cart. Before you begin you need:- A connected WhatsApp Business Account (WABA) — the WABA setup guide.
- A Meta Commerce Manager catalog attached to that WABA, with the
catalog_managementscope granted during the connect (standard for embedded sign-up). If a catalog never shows up in Orbit, disconnect and reconnect the channel — rotated tokens missing the scope are the most common cause. - An API key (
X-API-Key) or a dashboard session; owner/admin role for the send endpoints.
1. Catalog message vs template — which to choose
Templates are polished, pre-approved copy with buttons and variables. Product messages are live catalog cards. Pick per use case:
Two rules temper the trade:
- Outside the 24-hour service window Meta only permits templates. Product messages are free-form interactive messages, so plan them as replies — or open the conversation with a template first (a common pattern: template → customer replies → product_list “menu”). The 24-hour window guide explains the model.
- Catalog quality beats approval. Templates survive Meta review once; catalog cards render live from your feed, so a stale price or an empty
image_urlshows up in the customer’s chat as sent. Keep the feed honest (see Section 6).
2. The send endpoints
Three send routes, one list route, and a set of catalog mutation routes. Reads use the singular or plural alias interchangeably:
The two single-product sends are equivalent: the path form is convenient when your dashboard or script already knows which catalog it is drawing from, the body form keeps the URL stable across catalogs. Responses are the standard Orbit envelope —
data with the send result, meta with the request id.
Send a single product
product_retailer_id or the legacy product_id; in either form the value is the retailer id (SKU) your feed carries for the item. body is optional and defaults to “Check out this product” — keep it short; it renders as the message caption above the product card. Http(s) links you put in the body are automatically rewritten to trackable short links (unless your org toggled the WhatsApp URL-shortening opt-out), so a tap on them shows up in click analytics.
Send a multi-product list
header is expected (defaults to “Our products” when you omit it) and a body is required. Empty sections are dropped before the call reaches Meta. Both sends return:
order message — subscribe to the inbound event, not message.sent — the same flow the checkout guide folds into a cart.
3. Catalog binding and WABA scoping
Every catalog id you send or mutate is checked against your own WABAs before Orbit forwards it to Meta: the API enumerates the catalogs each of your connected WABAs owns and rejects an id that is not in that set with a 404NOT_FOUND — never a 403, so an id that belongs to someone else is indistinguishable from a typo. Product mutations resolve the product’s parent catalog first and apply the same check. A 60-second in-process cache keeps the Meta call off the hot path.
On tenants with multiple WABAs connected, pass waba_id or phone_number_id (as a body field, or as a query param on the GET list routes) to pick the connection; phone_number_id wins when both are supplied. Omitting both falls back to your tenant-default connection — the first connected one. A selector that matches no connection gets a 400 CHANNEL_UNAVAILABLE, never a silent wrong-WABA send:
4. The catalog rate-limit bucket
The three send endpoints share a dedicated bucket — 80 requests per minute per tenant, keyed on your tenant, not on theX-API-Key header or the caller’s IP. Dashboard sessions (cookie auth) and API-key callers draw from the same bucket, and the :wa-catalog suffix keeps it separate from the generic WhatsApp send bucket. Thirty to one-hundred list cards or sends a minute is comfortably above single-operator traffic; a 429 here almost always means a batch loop is retrying on a hard failure — fix the failure mode rather than loop-retrying.
5. Payloads and errors
The error envelope is stable across the surface —error.code tells you which gate rejected the send, and the status follows:
Two failure shapes deserve their own note:
- Meta accepted but returned no message id →
MESSAGE_SEND_FAILEDwith a 502, deliberately. A send without an id can never reconcile read/delivery, so Orbit refuses to report it as sent rather than leak a phantom success. Retry. - A partial catalog fetch — a malformed product row is dropped from the GET list (and the send on a dropped
retailer_idfails fast with a 422 rather than guessing). The rest of the list still returns.
6. Feed sync and reading catalog contents
The product list route reads live from Meta, so whatever Commerce Manager’s feed uploaded most recently is what the customer sees. Orbit does not own the feed — the Meta-side catalog feed is your pipeline (discreet uploads or a hosted feed on a schedule); what Orbit owns is the read-through and the sends. When you update a SKU in your commerce backend, push it to the Meta catalog, and the next send renders the new price without a template re-review. Read what the catalog currently holds before sending — the list route is also how you pick the retailer ids you will put into a send body:404 NOT_FOUND on a catalog_id in a send almost always means you forwarded an id from the wrong WABA, and an empty list means Meta’s Commerce Manager has no catalog bound to that WABA yet.
For catalog mutations — create a catalog, add/update/remove a product — use the routes tabled in Section 2. Sample requests for the lifecycle (create a product, then send it) also live in the WhatsApp API reference.
A test-account org (no own WABA yet) can only send products from the shared Devotel demo catalog, and only the demo SKUs. That gate exists so a demo WABA can never carry arbitrary product copies — once you complete embedded sign-up, own-WABA sends go straight through.
7. From product message to a paid order
The sends above are the commerce surface — the interactive bubble and the inboundorder it produces. Building the purchase (fold the order into a cart, resolve the checkout channel, reconcile the payment) is the conversational commerce checkout guide — it treats catalog sends as the entry point and the order webhook as the contribution. Read it for the cart state machine and the abandonment hook; use this page for the send and catalog mechanics.
Next steps
- Conversational commerce checkout — cart, native pay, hosted pay-by-link, reconciliation.
- WhatsApp API reference — the endpoint table including catalog mutations.
- The 24-hour window — when free-form product sends are allowed.
- WhatsApp pricing — what a service-window product message costs.
- WABA setup — connect the WABA the catalog lives on.