Digital products and subscriptions

Kiwify on WhatsApp: recover carts, Pix and boleto without writing code

Kiwify sends a JSON POST for every event on your product: boleto generated, Pix generated, abandoned cart, purchase approved, refused, refunded, chargeback and three subscription events. ZapZap has an assistant called Recuperar vendas (Recover sales), under Fluxos (Flows), that generates the webhook URL and a ready-made flow for Kiwify. You paste the URL into Kiwify, pick the event and the flow sends the message to the buyer's phone.

Updated on

Why connect

On Kiwify, sales are lost at three moments: the customer fills in the checkout and disappears, generates the Pix and does not pay, or the subscription renewal is refused. Each one has its own event, and each event can become a message within minutes, with the name and product already filled in. The flow ZapZap generates sends the first message right away and, for cart and Pix or boleto, a second reminder after a wait (30 minutes and 1 hour in the starter texts, which you edit). Message only people who started a purchase with you and include how to stop receiving messages. ZapZap connects numbers through an unofficial, managed channel, and pace matters. It costs R$15 per active number per month, prepaid, with a welcome credit when you create the account, subject to the current campaign.

Step by step

  1. 1

    Open the assistant in Fluxos

    In the ZapZap dashboard, go to Fluxos (Flows) and click Recuperar vendas. Choose Kiwify, the goal (abandoned cart, unpaid Pix or boleto, or post-purchase welcome) and the number that will send. The assistant creates the webhook, generates a flow already linked and enabled, and shows the URL, in the format https://api.zapzapapi.com/api/webhooks/flow/{secret}.

  2. 2

    Register the URL in Kiwify

    In Kiwify, open Apps, then Webhooks and Create webhook. Paste the ZapZap URL, pick the product (or all) and tick the events for the goal: carrinho_abandonado, pix_gerado and boleto_gerado, or compra_aprovada. Set a long, random token: without a token, the signature is computed with an empty key and proves nothing. Use Test Webhook to send a fake event.

  3. 3

    Turn on signature validation

    In the ZapZap assistant, enter the Kiwify webhook token. Kiwify does not send the token: it adds a signature parameter to the URL, an HMAC-SHA1 in hexadecimal computed over the exact request body, using the token as the key. With the token saved, ZapZap checks the signature of every event and answers 401 if it does not match. Without a saved token, only the URL secret protects the webhook.

  4. 4

    Check that the phone was found

    ZapZap keeps the last sample received and detects on its own where phone, name, product, email and amount are. After the first test, open the webhook in the editor and see whether the phone field appeared. If the ZapZap response comes back with triggered: 0, the flow did not fire: the event had no phone, the number is outside the 10 to 13 digit format, or the number already received this flow in the last 10 minutes.

  5. 5

    Edit the messages and run a test purchase

    The starter texts use {nome} and {produto}. Rewrite them in your own voice, remove anything that sounds like pressure and make a real low-value purchase, or use Test Webhook pointing to a number of yours. Look at the Kiwify logs under View logs: they show the body sent and the ZapZap response, and let you resend.

Example

# Simulates a Kiwify event on the ZapZap webhook URL (sample fields, use a number of yours).
# If you saved the token in the assistant, ZapZap answers 401 without the correct signature.
BODY='{"order_id":"test-001","order_status":"waiting_payment","webhook_event_type":"pix_created","Customer":{"full_name":"Maria Test","mobile":"+5511999990001"}}'

curl -X POST 'https://api.zapzapapi.com/api/webhooks/flow/YOUR_SECRET' \
  -H 'Content-Type: application/json' \
  -d "$BODY"
# response: {"ok":true,"triggered":1}

# To validate the signature on YOUR server (path without the assistant), Kiwify's calculation is:
# signature = HMAC-SHA1(key = webhook token, message = raw body), in hexadecimal
printf '%s' "$BODY" | openssl dgst -sha1 -hmac 'YOUR_KIWIFY_TOKEN' -hex

Illustrative. The exact fields and endpoints are in the documentation.

Kiwify trigger and the value that arrives in the payload

Kiwify warns that the trigger you tick and the webhook_event_type value in the body do not always have the same name. Anyone building a text filter needs the table below, which follows the official documentation.

Triggerwebhook_event_typeorder_status
boleto_geradobillet_createdwaiting_payment
pix_geradopix_createdwaiting_payment
compra_aprovadaorder_approvedpaid
compra_recusadaorder_rejectedrefused
carrinho_abandonadonot sentnot applicable (different payload format)
subscription_latesubscription_latestatus of the subscription's most recent order

Refused subscription: the event that saves recurring revenue

When a subscription renewal is refused, Kiwify sends compra_recusada and subscription_late for the same order, and repeats them on every failed new charge attempt. If the attempt limit runs out, the subscription is canceled and subscription_canceled arrives. It is the right window for a helpful notice ('your card did not go through, want to change the payment method?'). If the charge is refused before the end of the paid period, only compra_recusada arrives, and access remains valid: do not announce an access cut that does not exist yet.

The ZapZap assistant covers cart, Pix or boleto and post-purchase. For a refused subscription, the path is the generic webhook in the flow editor, with the webhook trigger and a filter on the webhook_event_type field, or an endpoint of yours.

Idempotency, resends and limits to know

  • Acknowledgment: a 2xx response confirms delivery. Any other status, a redirect or no response within 40 seconds counts as a failure, and Kiwify resends a few times, at intervals of 1 to 10 minutes.
  • Duplicates: the same event can arrive more than once. Kiwify itself recommends the combination order_id plus webhook_event_type as the key. ZapZap already ignores the repeat for the same flow and phone for 10 minutes.
  • Replay: Kiwify's signature carries no date or delivery id, so a captured and resent request stays valid. If you need certainty about a sale's state, query GET /sales/{id} on the Kiwify API.
  • Token change: there is no period with both tokens valid. Accept both on your server for a day, or wait for pending resends to finish.
  • ZapZap limit: 120 requests per minute per webhook. A big launch can exceed that; above the limit the response is 429 and Kiwify tries again.

When NOT to use the assistant

If you need to build a message with data the assistant does not extract (recovery link, a rule by amount range, a query to your database), the assistant falls short and the best option is an endpoint of yours or a flow in n8n. If the product is an affiliate product, the producer must release the buyer's data for the phone to reach the webhook. And on a cold list that never started a purchase, do not use this path: that is prospecting, which has a different risk and a different legal basis.

Sources and date checked

Triggers, webhook_event_type values, the HMAC-SHA1 signature in the query, resends, idempotency and subscription events were checked on 29 Oct 2026 in the official Kiwify documentation (docs.kiwify.com.br), on the pages Create webhook, Receive and validate webhooks and Subscription events, and in the Kiwify help center. The Customer block of the payload is summarized in the documentation; confirm the exact field names with Test Webhook and View logs. ZapZap's behavior comes from the product code in production.

Frequently asked questions

Does Kiwify send the token with the webhook?

No. The token does not go in the header, the query or the body. It is the key used to sign. What Kiwify sends is the signature parameter in the URL, an HMAC-SHA1 in hexadecimal of the exact body. To validate, compute the HMAC of the raw body with the token and compare in constant time.

Does the abandoned cart event have order_status?

No. The abandoned cart payload has a different format, with cart data and its own status, and it has neither webhook_event_type nor order_status. If you filter on those fields, handle this event separately.

The flow did not fire and the response was triggered: 0. What should I check?

Three common causes: the payload has no phone with 10 to 13 digits, the phone came from the wrong field, or the same number already received this flow in the last 10 minutes. Open the webhook sample in the editor and see which field ZapZap detected as the phone.

Do I need to know how to code?

For cart, Pix, boleto and welcome messages, no: the assistant builds the flow and the URL. Coding becomes necessary if you want your own rules, to query your database or to handle a refused subscription with custom logic.

Does Kiwify have a test environment?

No. The documentation says there is no sandbox or test mode for the API or the webhooks. The Test Webhook button sends fake data in the same format, and that is how you validate reception before a real sale.

Does a refund cancel the subscription on its own?

Yes, according to Kiwify. A refund or chargeback of a subscription charge cancels the subscription, and two webhooks arrive: the purchase one and subscription_canceled. On a chargeback, customer_access is not zeroed, so remove access based on the chargeback event.

Can I send the cart message to someone who did not finish the purchase?

You can when the person left their phone at your checkout, which creates a started-purchase relationship. Be brief, offer help and let them stop receiving messages. ZapZap does not remove the blocking risk of an unofficial channel, and LGPD (Brazil's data protection law) asks for a clear legal basis for contact.

Start now

Create your account, connect a number in minutes and test the API with warm-up and anti-ban. R$15 per active number/month, prepaid, no lock-in.