Traffic

How to measure ad CPL on WhatsApp with the conversion API

For ads that lead to WhatsApp, the metric the platform shows is a conversation started. What the agency's client wants to know is how much each sale cost. Between the two sits the CRM: who became a sale, from which ad, for which client. ZapZap exposes a call to say that an ad lead bought, and the event goes back to Meta so it learns who really buys. This guide shows the math, the call, the errors it returns and the limits. Nothing here promises campaign results: optimization depends on the account, the audience and the creative.

Published on · 4 min read

This article is also available in: Português · Español

The math: CPL, cost per sale and what is still missing

CPL is spend divided by the number of leads. Cost per sale is spend divided by the number of sales. A hypothetical example, just for the arithmetic: R$2,000 of spend, 160 leads and 8 sales give a CPL of R$12.50 and a cost per sale of R$250. With an average ticket of R$700, revenue is R$5,600 and the ratio of revenue to spend is 2.8. The numbers are illustrative and do not represent any client's result.

CPL alone misleads: an ad with a low CPL can bring curious people who never buy. That is why the loop only closes when the sale returns to the ad system. See also the flow for ad leads per client and the page for traffic managers.

The call: POST /{instanceId}/tracking/conversion

The reference describes the operation as registering a sale from an ad lead. The fields are phone (required, with country and area code, for example 5511999998888), event, value and occurred_at. The event accepts Purchase (default), Schedule, Lead and QualifiedLead; Purchase marks the lead as won, Schedule as scheduled and the others as qualified. The value accepts a number or text such as "700,50".

occurred_at records when the sale happened and feeds your report. The send to Meta uses the moment of registration, because Meta rejects old events. So the right path is to call at the instant payment is confirmed in your system, not in a batch at month end.

curl and Node.js: mark a sale

# Mark a sale from an ad lead (event is optional, default Purchase)
curl -X POST "https://api.zapzapapi.com/api/v1/INSTANCE_ID/tracking/conversion" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $ZAPZAP_API_KEY" -H "x-api-secret: $ZAPZAP_API_SECRET" \
  -d '{"phone":"5511999998888","event":"Purchase","value":700.50,"occurred_at":"2026-11-10T14:30:00Z"}'

// Node: call it when your payment system confirms, and handle the documented errors
async function markSale(instanceId, phone, value) {
  const res = await fetch("https://api.zapzapapi.com/api/v1/" + instanceId + "/tracking/conversion", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.ZAPZAP_API_KEY,
      "x-api-secret": process.env.ZAPZAP_API_SECRET,
    },
    body: JSON.stringify({ phone, event: "Purchase", value }),
  });
  const body = await res.json().catch(() => ({}));
  if (res.ok) return body;                                   // { ok: true, status: "queued", ... }
  if (res.status === 404 && body.error === "LEAD_NOT_FOUND") return null; // not an ad lead: nothing to attribute
  if (res.status === 409 && body.error === "TRACKING_DISABLED") throw new Error("turn on Meta tracking for this instance");
  throw new Error(body.error + ": " + (body.message || res.status));
}

The errors the call returns (and what to do for each)

404 LEAD_NOT_FOUND: that phone did not come from an ad on this instance, so there is nothing to attribute. It is not a failure: treat it as an organic sale and move on. The search tolerates the ninth-digit difference, but check that the sale's number is the same as the chat's.

409 TRACKING_DISABLED: tracking is not active on the instance. Turn on the tracking setting in the panel first. 409 EVENT_NOT_ACCEPTED: the Pixel and token are not saved, or that event type is off. 400 INVALID_PHONE, INVALID_EVENT or INVALID_VALUE: malformed data, fix it at the source. The guide on instances per client helps you know which instance to use for each sale.

Idempotency and repeat purchases

The reference states that calling twice for the same sale does not duplicate the event, which protects you from retries by your CRM. Even so, keep the sale id on your side and call only once per confirmed payment.

A second purchase by the same customer is another event and should be sent when it happens. Do not block repeat purchases in your system: they are the most valuable signal for the algorithm.

Building the monthly report per client

Join three sources: spend (from the ads manager), leads (conversations started by ads) and sales (the conversion calls). The per-client table shows spend, leads, CPL, sales, cost per sale and revenue. The client portal shows leads and sales with their brand, and the text report is a complement. See automating reports.

Agree with the client on who marks the sale and when: if the sale closes outside WhatsApp, someone has to tell the system. Without that, cost per sale comes out higher than reality and the ad looks worse than it is. See also the WhatsApp API and the reference.

Quick summary

  • CPL is spend per lead; what decides is cost per sale, and for that the sale must return to the system.
  • POST /{instanceId}/tracking/conversion accepts Purchase, Schedule, Lead and QualifiedLead, with phone in international format.
  • 404 LEAD_NOT_FOUND means a sale with no ad origin on this instance, and 409 TRACKING_DISABLED asks you to turn tracking on.
  • Call at the instant of payment confirmation, once per sale, and send repeat purchases as a new event.
  • The numeric examples are hypothetical: the platform does not promise campaign results.

Also read

Frequently asked questions

Can any sale be marked through the API?

Only one from a lead that arrived through an ad on this instance. For other phones the call returns 404 LEAD_NOT_FOUND, which indicates a sale with no ad origin.

Do I need to configure anything before calling?

Yes. Meta tracking must be active on the instance, with the Pixel and token saved and the event type turned on. Without that the API returns 409.

Can I send the real date of the sale?

Yes, in occurred_at (ISO 8601 or epoch in seconds). It feeds your report, but the send to Meta uses the moment of registration.

Does calling twice duplicate the sale?

According to the reference, no: calling twice for the same sale does not duplicate it. Even so, avoid repeating it.

Does this guarantee more sales?

No. Sending the sale back gives the ad system a better signal, but the result depends on account, audience, offer and customer service.

Close the loop between ad and sale

Create the account, connect the client's number and turn tracking on. R$15 per active number per month, with no per-message fee. When you verify your phone there is a welcome credit, subject to the current campaign.