Invoices, Pix and boleto

Eduzz on WhatsApp: paid invoice, boleto and cart with x-signature validated

Eduzz sends webhooks from MyEduzz and from checkout, each with an id, the event name, a data block and sentDate, and signs every delivery with the x-signature header, an HMAC-SHA256 of the body. ZapZap has Eduzz in the Recuperar vendas (Recover sales) assistant: it generates the webhook URL and the message flow, and validates x-signature when you provide the secret key.

Updated on

Why connect

Eduzz has two events that make life easier for anyone recovering sales: the open invoice (invoice_opened) already carries the recovery URL (saleRecoveryUrl) and the boleto link (bankslipUrl), and the abandoned cart (cart_abandonment) carries the checkout URL for the person to come back. With that, the message needs no extra lookup: the link is already in the payload. Message only people who started a purchase, with a way to stop receiving messages. ZapZap connects numbers through an unofficial, managed channel. 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

    Create an access key in Eduzz

    In the Eduzz developer Console, open the webhook Security screen and generate a key. You can register as many as you want, one per destination, which helps when the same event goes to different systems. Eduzz signs every message with that key and sends the result in the x-signature header.

  2. 2

    Create the webhook setting

    In the Settings menu, click New setting: name, access key, URL to send the data to, and the events you want. Eduzz recommends testing the URL, which must answer 200 to 299. There is a field to limit the maximum number of parallel events to your URL, useful during a launch. The webhook only sends events for the products of the account you are logged in with.

  3. 3

    Generate the URL in ZapZap and save the key

    In Fluxos, Recuperar vendas, choose Eduzz, the goal and the number, and paste the generated URL (https://api.zapzapapi.com/api/webhooks/flow/{secret}) into Eduzz. Save the secret key in the assistant: ZapZap computes the HMAC-SHA256 of the raw body, accepts the result in hexadecimal or base64 and rejects with 401 whatever does not match.

  4. 4

    Answer the ping and check the phone

    When you activate or verify the URL, Eduzz sends a ping event, with data.message equal to ping, and expects a 2xx. In ZapZap, the ping has no phone and answers triggered: 0, which is expected. Then fire a real test event and check, in the webhook sample, which field was detected as the phone (data.buyer.cellphone, data.buyer.phone or data.customer.phone on the cart).

  5. 5

    Use the payload links in the message

    In the flow editor, the starter text uses {nome} and {produto}. To include the recovery or boleto link, the path is an endpoint of yours, which reads data.saleRecoveryUrl or data.bankslipUrl and sends through the ZapZap API, as in the example below.

Example

// Validates Eduzz's x-signature and sends the recovery link through ZapZap
import crypto from 'node:crypto';
import express from 'express';
const app = express();

app.post('/eduzz', express.raw({ type: 'application/json' }), async (req, res) => {
  const expected = crypto.createHmac('sha256', process.env.EDUZZ_KEY).update(req.body).digest('hex');
  const received = String(req.get('x-signature') || '');
  const ok = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const ev = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200);
  if (ev.event === 'ping' || (await alreadyProcessed(ev.id))) return; // your function: UNIQUE on the id

  const d = ev.data;
  const phone = String(d.buyer?.cellphone || d.buyer?.phone || d.customer?.phone || '').replace(/\D/g, '');
  const link = d.saleRecoveryUrl;
  if (!phone || !link) return;

  await fetch('https://api.zapzapapi.com/api/v1/' + process.env.ZAPZAP_INSTANCE + '/send/text', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.ZAPZAP_API_KEY,
      'x-api-secret': process.env.ZAPZAP_API_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ number: phone.startsWith('55') ? phone : '55' + phone, text: 'Hi! Your order was left half done. Pick it back up here: ' + link }),
  });
});

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

Eduzz events that become messages

Eduzz's public reference lists dozens of events. The ones in the table fit billing and recovery best, with the phone field the reference documents.

GoalEventPhone and links in the payload
Payment confirmedmyeduzz.invoice_paiddata.buyer.cellphone, phone and phone2; data.paidAt; data.paymentMethod (bankslip, pix, creditCard...)
Open invoicemyeduzz.invoice_openeddata.buyer.cellphone; data.saleRecoveryUrl; data.bankslipUrl
Expired invoicemyeduzz.invoice_expiredsame invoice envelope; use it for the last reminder
Abandoned cartsun.cart_abandonmentdata.customer.phone; data.saleRecoveryUrl; data.productId
Refund and disputemyeduzz.invoice_refunded, myeduzz.invoice_chargebackFor removing access, not for selling

x-signature in practice

According to Eduzz, x-signature is hmac('sha256', secret_key, request_body); if the result is identical to the header, the message is legitimate. The documentation does not say whether the result comes in hexadecimal or base64, so ZapZap accepts both. In your endpoint, compute over the raw body (the received bytes), before any JSON parsing, and compare in constant time. Recomputing over a re-serialized JSON changes spacing and key order and the signature stops matching.

  • Key: one per destination. If a key ever leaks, generate another and point the setting to it.
  • Replay: the envelope has sentDate, but the documentation does not say it is part of the signature. Use the event id to discard repeats.
  • Parallelism: the setting's parallel events limit protects your server during a launch. ZapZap accepts 120 requests per minute per webhook; above that it answers 429.

When NOT to use the assistant

The assistant builds messages with name and product. If you want to put saleRecoveryUrl or bankslipUrl in the text, or vary the message by data.paymentMethod, use an endpoint of yours or n8n. Also do not use refund and chargeback events for marketing messages: contact through those events is for dealing with the customer's problem. And do not message people who never started a purchase; that is prospecting and needs a different basis.

Sources and date checked

Events, phone fields, saleRecoveryUrl, bankslipUrl, the x-signature signature, setting creation and the ping event were checked on 29 Oct 2026 in the Eduzz Developer Hub (developers.eduzz.com), on the Webhook pages (Creating a webhook, Security, Verification Event) and in the public OpenAPI specification of the webhooks. ZapZap's behavior comes from the product code in production.

Frequently asked questions

How does Eduzz prove the webhook is its own?

By sending the x-signature header, an HMAC-SHA256 of the request body computed with the secret key you registered. You recompute over the raw body and compare; if it is equal, the message is legitimate. ZapZap does this check when you save the key in the assistant.

Why do I receive a ping event with no phone?

The ping is for verifying the URL: it has data.message equal to ping and expects a 2xx. It is not a sale. In ZapZap it answers triggered: 0, which is normal, and in your endpoint you should ignore it.

Which field has the buyer's mobile number?

On invoice events, the reference documents data.buyer.cellphone, data.buyer.phone and data.buyer.phone2. On the abandoned cart the field is data.customer.phone. ZapZap prefers the mobile when both exist, but check the sample to see which one was detected.

Can I send the sale recovery link?

The payload of the open invoice and of the abandoned cart carries data.saleRecoveryUrl. To put that link in the text, use an endpoint of yours or n8n, which read the field and call the ZapZap API. The assistant's text is fixed with name and product.

My server cannot handle a launch's volume. Now what?

The webhook setting in Eduzz has a limit on events sent in parallel to your URL. Set a value your server can handle and let an internal queue take care of the rest. Answer 2xx fast and send the WhatsApp in the background.

Does Eduzz send events from other accounts?

No. The webhook only sends events for the products that belong to the account used in the setting. If events are missing, check that you are logged in with the account that owns the products.

Can I use this to remind people who have not paid for weeks?

With care. The expired invoice reminder should be short, respectful and include a way to stop receiving messages. Billing that exposes or embarrasses the debtor is a legal problem on top of a blocking risk, and ZapZap, an unofficial channel, does not guarantee the number will not be limited.

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.