Overview
Sales recovery is the layer that makes sure a commerce event always becomes a visible action. Previously the recovery card could die in silence: if the customer had no open conversation, or if the message fell outside the WhatsApp window, nothing happened and nobody found out.
Now every lifecycle event β from Kiwify, Hotmart, Nuvemshop or the native gateway (Asaas / Mercado Pago) β goes through three guarantees:
- A conversation resolver decides where the card goes, using a policy you choose.
- A send-window guard checks whether the channel accepts the message at that moment.
- A delivery outcome is recorded on every exit path:
sent,skippedorfailed, always with a readable reason.
The practical result: you open the Sales recovery page and see how many opportunities were reached, how many were not, and exactly why β instead of discovering weeks later that an entire cadence never went out.
When each stage fires
| Stage | Fires when | Actionable? |
|---|---|---|
| Abandoned cart | the customer built a cart/checkout and did not complete it | Yes |
| Payment pending | a PIX or boleto was generated and is still unpaid | Yes |
| Payment declined | the card was refused by the issuer or by anti-fraud | Yes |
| Overdue | the due date passed without payment | Yes |
| Order placed | the order was registered on the platform and remains pending | No (does not settle) |
| Payment confirmed | the payment was approved | No (settlement) |
| Refunded | the amount was returned to the customer | No |
| Chargeback | the customer disputed the charge with the issuer | No |
| Subscription late | the subscription renewal failed or is late | No |
| Subscription canceled | the subscription was terminated | No |
The four actionable stages are the ones that count toward the recovery rate. Refunds and chargebacks never count as recovery β they are the opposite of it.
due_soon remains a recognized stored value for backwards compatibility, but no current connector or
native gateway produces it. It is therefore not offered in automatic recovery or the Follow-up picker.
Prerequisites
- The Sales recovery feature (
commerce_recovery) enabled on the account. It ships OFF by default; ask whoever administers the installation to turn it on. - An administrator or agent profile on the account. Both can open the recovery page, change policies and resend cards β there is no separate per-module permission here.
- At least one commerce source connected (Kiwify, Hotmart, Nuvemshop) or a native gateway configured (Asaas / Mercado Pago), so that events arrive.
- To send outside the WhatsApp window: an approved template on the matching inbox.
With the feature off, the new page, the sidebar panel and the metrics stay hidden. Delivery itself (conversation policy, recorded outcome) keeps working behind the scenes β it is behavior-preserving infrastructure.
Step by step
- Open Sales recovery from the commerce module menu.
- Check the actionable stage funnel: it uses the same four stages as attempted and recovered metrics; settlements, refunds and cancellations do not enter this funnel.
- Look at the delivery block:
sent,skipped,failedandnot attempted. - Open the reasons list β it is ordered from most to least frequent. That is your fix list, in order of impact.
- Click an event to see the card, the target conversation and the delivery history.
- If the card did not go out for a reason you have already fixed (channel reconnected, template approved), use Resend. If you need to bypass the duplicate guard, tick force.
- Adjust the conversation policy on the automation/macro action so the next event of the same type does not fall into the same reason.
Settings & options
The conversation policy (the most important choice)
When the card must be delivered, the platform needs to know which conversation to write into. You have three options:
| Policy | What it does | When to use it |
|---|---|---|
| Use an existing conversation (default) | Delivers into the contact's most recent conversation. Never creates one. If there is none, the card is skipped with reason no_conversation. |
The safe default β it is exactly today's behavior. Nothing changes for existing setups. |
| Create one if needed | Uses the existing conversation when there is one; creates a new one when there isn't. | When you want maximum reach and accept that a new conversation becomes visible to the customer. |
| Only this conversation | Delivers strictly into the conversation where the rule fired. Never looks elsewhere, never creates. | When the card only makes sense in the context of that specific conversation. |
Why "create one if needed" is opt-in: creating a conversation is an action that is visible to the customer and to the team's queue. It shows up in the inbox, counts in reports and can trigger notifications. That is why Conversa Labs never does it on its own β you have to choose it.
The WhatsApp 24-hour window (no half-truths)
WhatsApp only allows free-form messages within 24 hours of the customer's last message. Outside that window:
- Without an approved template β the card is skipped, with reason
whatsapp_window_closed. It is not delivered. Conversa Labs would rather record the reason than queue a message WhatsApp is going to reject. - With an approved template β delivery degrades to the single template message. You reach the contact, but not with the full rich card: only what the approved template allows.
WhatsApp Web has no window. WhatsApp Web inboxes deliver normally at any time β the 24-hour restriction belongs to the official WhatsApp Business API, not to the platform.
Exception: hybrid pair. If you run a hybrid pair with Cloud as primary and out-of-window routing to
WhatsApp Web, the send goes out in full as a Web session message β it is neither converted to a template
nor skipped. Do not expect whatsapp_window_closed in that setup.
Other channels
The card always carries the payment link in the message body, never only as an attachment. This is deliberate: LINE, TikTok and X (Twitter) drop or reject attachments. If the link travelled only in the attachment, the customer would receive a message missing the one thing that matters.
Card delivery and the "not attempted" queue
The panel shows the delivery split β sent, skipped, failed and not attempted. The "not attempted" bucket is the one that never got a card at all: no rule ever acted on that event. It is no longer a dead number: you can filter the timeline by it and queue the pending ones in bounded batches, oldest first, up to 50 at a time.
The action returns accepted/queued, not a delivery total. Processing happens in the background and
the actual sent, skipped or failed outcomes appear later in the timeline. It never opens a new
conversation: an event with nowhere to deliver is recorded as skipped with the corresponding reason.
Imported or gateway-adopted history is always excluded from this queue.
Why "Recoveries attempted" can read 0 with a full list. The count only considers actionable events whose card was recorded as sent. If no card was ever sent, the denominator is zero β the page is not broken, it is telling you nobody has been reached yet.
Automatic recovery by stage (default off)
Under Messages β Automatic recovery by stage, enable only the live stages you want Conversa Labs to queue automatically. Every switch starts off. The immediate lifecycle event queues the card, and a five-minute backstop repairs a missed queue handoff. Both paths re-check the switch before delivery, reuse an existing conversation, respect the WhatsApp 24-hour window, and defer when the connection's safe send rate is full.
Imported and adopted charges are history: they stay visible for audit/reporting with the Historical β sending blocked badge, but are excluded from every customer send. Automatic recovery, manual backlog drains, the backstop, Follow-up, forced resend, automations and Maestro cannot bypass this guard.
The order of the card's messages
The card is a sequence: summary β pay button β PIX copy-and-paste β QR code β boleto β digitable line. That order is now guaranteed on delivery β each message used to be sent on its own and they could arrive shuffled (the raw PIX code reaching the customer before the message telling them to copy it).
Under Messages β Delivery you set the pause between the card's messages. Leave it blank to use the channel default: on phone-linked WhatsApp (WazMeow) it is 1 second, to space the burst out and not look like an automated blast; on other channels there is no pause.
Opening the conversation and resending
Every live row has Open conversation (jumps straight to that customer's thread) and Resend card. The resend targets the conversation by its public identifier β it can never land on the wrong customer. If the event has no conversation yet, the dialog says so before you confirm that one will be opened with the customer. Historical rows do not show resend, and the API also refuses it even with force.
Use cases
- A pending PIX that went cold: the customer generated the PIX yesterday and vanished. The card resends the code into the existing conversation, without creating new noise.
- A wave of card declines: an issuer knocked down several transactions. You filter by
payment_declined, see they were all skipped withchannel_unavailable, reconnect the channel and resend in bulk. - An abandoned cart from someone who never talked to you: a new contact, no conversation. With the "create one if needed" policy, the card opens the conversation and starts the support thread.
- Cadence audit: the reasons list shows 60% of sends died on
whatsapp_window_closedβ the clear signal that this cadence needs an approved template.
Tips, limits & best practices
The delivery outcomes and what to do about each
Every event ends in one of these states: sent, skipped, failed β or not attempted, when no rule ever acted on it.
| Reason | What it means | What to do |
|---|---|---|
no_contact |
The event arrived with no identifiable contact (the source platform sent no usable phone/email). | Check the identification mapping on the commerce source. With no contact there is nobody to send to. |
no_conversation |
The contact exists, but there is no conversation to receive the card and the policy is "use an existing conversation". | If you want to reach these cases, switch the policy to create one if needed. |
no_channel_inbox |
There is no inbox for the channel the action asked for. | Connect that channel's inbox, or point the action at an inbox that exists. |
whatsapp_window_closed |
Outside the 24-hour window and without an approved template. | Attach an approved template to the action. Or move the cadence inside the window. |
throttled |
The channel's send limit was reached at that moment. | Space the cadence out. Large bursts on WhatsApp also increase the risk of a block. |
channel_unavailable |
The channel is disconnected, expired or unavailable. | Reconnect the inbox and resend the affected events. |
sequence_not_published |
The Follow-up sequence is still a draft. | Publish the sequence. Drafts never send β that is intentional. |
already_sent |
The duplicate guard blocked it: this event already had a card recorded as sent. | Nothing, in most cases. If you really must send again, use Resend with force. |
Integrating with external systems
Every delivery outcome β sent, skipped or failed β is also emitted as the commerce_card_delivery
account webhook event, carrying the canonical reason. That is how an external system (n8n, a CRM)
reacts without polling: open a task when the reason is no_conversation, say, or try another channel
when it is whatsapp_window_closed. Enable it under Settings β Integrations β Webhooks.
The duplicate guard and explicit resend
A single event creates and dispatches its card locally once. If an automation and a macro both try
to send the same card, the second is skipped with already_sent β that is the local duplicate guard.
Final provider delivery still has to be checked in the conversation/channel delivery state.
Resending is always explicit and human: you open the event and click Resend. It preserves the record of the first send (original date and message) and only increments the resend counter β history is never erased. To cross the guard on purpose, tick force.
How to read the recovery rate (honestly)
The recovery rate is the share of actionable attempts recorded as sent and assigned to a later settlement for the same contact and the same order.
Precisely:
- Denominator: actionable events (abandoned cart, pending, declined, overdue) whose card was recorded as sent.
- Numerator: later settlements with the same contact, source and external order/charge id. Paying a different order does not recover the first one.
- A settlement counts once. If one order received several actionable cards, it is assigned
deterministically to the latest attempt recorded as sent before settlement. Order placed never
settles revenue: only
payment_confirmedenters the numerator. - Events whose card was never recorded as sent are excluded from both sides. The
sentstate confirms local card creation and dispatch; it is not yet a final provider delivery receipt. - When nothing was attempted in the period, the rate shows as β, not as 0%. Zero percent would mean "we tried and failed"; the dash means "there was no attempt".
This is correlation, not causation. The metric says "the customer paid after we reached out", not "the customer paid because we reached out". Some of those people would have paid anyway. Use the number to compare cadences against each other and to track a trend β do not present it as revenue attributed to a campaign.
Recovered revenue uses the settlement's value and currency, not the value shown on the card. Totals are displayed separately per currency β BRL and USD are never added together or labelled as if everything were BRL. Values remain in each currency's major unit, with no hidden conversion. When the source did not report a currency, the interface states that absence explicitly.
Troubleshooting
- "Not attempted" on many events: no rule is acting on that stage. Create an automation or a Follow-up sequence for the stage in question.
- Everything skipped with
no_conversation: your base is contacts with no open conversation and the policy is the default. Switch to create one if needed β remembering the new conversation is visible to the customer. - Everything skipped with
whatsapp_window_closed: the cadence is running outside the 24-hour window. Approve a template and attach it to the action, or move the trigger earlier. - Card delivered, but "poor": you are outside the window with a template. That is the correct behavior β WhatsApp only accepts the approved template in that situation.
- Intermittent
channel_unavailable: the inbox is dropping. Check the channel connection before resending in bulk, otherwise the resends fail for the same reason. - The customer received it twice: check whether an automation and a Follow-up sequence both cover the same stage, or whether someone used force on a resend.
- The rate shows "β": no recovery card was recorded as sent in the filtered period. Widen the period or check the reasons list.
- The page does not appear: the
commerce_recoveryfeature is off on the account, or your user is neither an administrator nor an agent on it.