← Blog

·

Dynamic URL button with UTM in WhatsApp templates: track clicks and sales in GA4

Updated on October 29, 2026

To measure clicks and sales from a WhatsApp template in GA4, use a URL button with a dynamic suffix: the approved template has a fixed address ending in {{1}}, and on each send you pass a value that identifies the campaign (and, if you want, the individual send). That value becomes UTMs (utm_source=whatsapp, utm_medium, utm_campaign), either directly in the URL or through a redirect on your own domain, and GA4 attributes the session and the purchase to the campaign. On the WhatsApp side, you match this with the status webhook and Meta’s template analytics to get the full funnel: delivered, read, clicked and sold.

This guide covers Meta’s rules for URLs in templates, two ways to set up UTMs, the send payload and a report template.

Meta’s rules for the URL button

According to the template components documentation:

RuleLimit
URL buttons per templateUp to 2
Button textUp to 25 characters
URL lengthUp to 2,000 characters
Variables in the URLAt most 1, always at the end of the address
Sample valueRequired when the URL has a variable
Special charactersMust be percent-encoded (e.g., a space becomes %20); unencoded characters can make the send fail

The practical consequence matters: you can’t put the variable in the middle of the URL or have two variables (one for the campaign and one for the contact). All tracking has to fit in a single suffix at the end.

Two ways to pass UTMs

Option 1: fixed UTMs in the URL, variable in the last parameter

The template is created with the campaign’s UTMs, and the variable fills only the last parameter:

https://www.store.com/offers?utm_source=whatsapp&utm_medium=template&utm_campaign=bf2026&utm_content={{1}}

On send, {{1}} gets something like sneaker_offer_a. Pros: simple, no server needed. Cons: utm_campaign is locked into the template, so each new campaign requires a new template (and a new approval); and the value you pass must be URL-encoded.

Option 2: redirect on your own domain (recommended)

The template has a short URL on your own domain:

https://www.store.com/w/{{1}}

On send, {{1}} gets a short code, such as k7Qm2x. Your server stores what that code means (campaign, template, destination, send ID) and replies with a 302 redirect to the final URL with all the UTMs. Pros:

  • one template serves many campaigns, with no new approval;
  • you can change the destination after sending, if the page changes;
  • your server logs the click instantly, with the send’s code;
  • the customer sees your company’s domain, not an unfamiliar shortener.

Meta’s documentation doesn’t address third-party URL shorteners. Using your own domain avoids the question and makes it clear to the customer where they’re going.

A minimal redirector in Node.js (Express):

import express from 'express';
const app = express();

app.get('/w/:code', async (req, res) => {
  const link = await db.links.findOne({ code: req.params.code });
  if (!link) return res.redirect(302, 'https://www.store.com/');

  await db.clicks.insert({ code: link.code, wamid: link.wamid, at: new Date() });

  const url = new URL(link.destination);
  url.searchParams.set('utm_source', 'whatsapp');
  url.searchParams.set('utm_medium', 'template');
  url.searchParams.set('utm_campaign', link.campaign);
  url.searchParams.set('utm_content', link.template);
  url.searchParams.set('utm_id', link.campaign_id);
  res.redirect(302, url.toString());
});

Want to try it while you read? Create a Vectalk account and send your first message through the official API. Create free account →

How to build UTMs for GA4

GA4 recommends always setting utm_source, utm_medium and utm_campaign, and also suggests utm_id (Google Analytics help on UTMs). Values are case-sensitive: whatsapp and WhatsApp become two different sources. Pick a convention and use lowercase.

ParameterSuggested valuePurpose
utm_sourcewhatsappTraffic source
utm_mediumtemplate (marketing) or utilitySeparate campaigns from transactional notices
utm_campaignbf2026, winback_octCampaign name
utm_contenttemplate name or test variantCompare copy and buttons
utm_idcampaign ID in your systemJoin with cost data

About channel groupings: in GA4’s default channel group, the channel rules look at source and medium. With utm_medium=template, traffic may land in “Unassigned” or in another channel, depending on the rules and the list of sites Google maintains. To see WhatsApp as its own channel, create a custom channel group with the condition “Session source exactly matches whatsapp.”

Don’t put phone numbers, names, emails or other personal data in UTMs. Google prohibits sending personally identifiable information to Analytics, and the URL also stays in the browser history. To link a click to a contact, use the redirect’s opaque code and store the mapping in your database.

The template and the send payload

Template creation (same format as the Cloud API), with a body, a dynamic URL button and an opt-out button:

{
  "name": "weekly_offer_url",
  "language": "en_US",
  "category": "MARKETING",
  "components": [
    {
      "type": "BODY",
      "text": "Hi, {{1}}! The kit you looked at is 20% off until Sunday.",
      "example": { "body_text": [["Ana"]] }
    },
    {
      "type": "BUTTONS",
      "buttons": [
        {
          "type": "URL",
          "text": "See offer",
          "url": "https://www.store.com/w/{{1}}",
          "example": ["k7Qm2x"]
        },
        { "type": "QUICK_REPLY", "text": "Stop offers" }
      ]
    }
  ]
}

Sending to a contact, passing the name in the body and the code in the button. index is the URL button’s position in the list (starting at 0):

{
  "messaging_product": "whatsapp",
  "to": "5511999999999",
  "type": "template",
  "template": {
    "name": "weekly_offer_url",
    "language": { "code": "en_US" },
    "components": [
      {
        "type": "body",
        "parameters": [{ "type": "text", "text": "Ana" }]
      },
      {
        "type": "button",
        "sub_type": "url",
        "index": "0",
        "parameters": [{ "type": "text", "text": "k7Qm2x" }]
      }
    ]
  }
}

The flow in your system, per contact:

  1. Generate a unique short code and save code → campaign, template, destination.
  2. Send the template with the code in the button parameter.
  3. Store the wamid returned by the API alongside the code.
  4. Update delivered, read or failed when the status webhook arrives.
  5. Log the click in the redirector and let GA4 handle the session and the purchase.

If you’re sending to many contacts, generate the codes in bulk before the campaign. The article on how to send a template through the API details the other payload components.

Measuring clicks through Meta

Besides your redirector, Meta counts button clicks. According to the analytics documentation, template analytics return clicks on URL and quick reply buttons for marketing and utility templates, with the types url_button (total) and unique_url_button (distinct accounts that clicked). Three details:

  • you must first enable template analytics on the account (in WhatsApp Manager or with is_enabled_for_insights=true), and once enabled they can’t be turned off;
  • read and click data is available for up to 7 days from the send date;
  • click tracking can be turned off per template with cta_url_link_tracking_opted_out.

Sample query (with placeholder IDs):

curl -g 'https://graph.facebook.com/v26.0/<WABA_ID>/template_analytics?start=1793145600&end=1793232000&granularity=daily&metric_types=sent,delivered,read,clicked&template_ids=[<TEMPLATE_ID>]' \
  -H 'Authorization: Bearer <TOKEN>'

Meta’s numbers and your redirector’s don’t have to match exactly: they’re different measurements. Use Meta to compare templates against each other and your redirector to link clicks to sends and sales.

Report: from send to sale

With the redirect code, you can bring everything together in one table per campaign. Example in SQL, assuming the tables sends (one row per message, with the link code and the latest webhook status), clicks (redirector) and orders (with the link code saved at checkout):

SELECT
  s.campaign,
  COUNT(*) FILTER (WHERE s.status IN ('delivered', 'read')) AS delivered,
  COUNT(*) FILTER (WHERE s.status = 'read') AS read,
  COUNT(c.code) AS clicked,
  COALESCE(SUM(o.orders), 0) AS orders,
  COALESCE(SUM(o.revenue), 0) AS revenue,
  ROUND(COUNT(*) FILTER (WHERE s.status IN ('delivered', 'read')) * 0.3217, 2) AS meta_cost_brl
FROM sends s
LEFT JOIN (SELECT DISTINCT code FROM clicks) c ON c.code = s.code
LEFT JOIN (
  SELECT link_code, COUNT(*) AS orders, SUM(amount) AS revenue
  FROM orders GROUP BY link_code
) o ON o.link_code = s.code
GROUP BY s.campaign;

The result becomes a report like this (fictional example, just to show the format):

CampaignDeliveredReadClickedOrdersRevenueMeta cost
bf2026_sneakers4,0002,90061048R$ 9,120.00R$ 1,286.80
winback_oct1,500820957R$ 980.00R$ 482.55

The cost uses Meta’s marketing rate for Brazil in effect since October 1, 2026 (R$ 0.3217, in Brazilian reais, per delivered message); for other countries, use the rate for the recipient’s country. In GA4, the same analysis shows up in the traffic acquisition report filtered by Session source = whatsapp, with the purchase event marked as a key event.

One caveat: without user identification (User-ID), GA4 generally only attributes the sale if it happens in the same browser as the click. If the customer clicks on their phone and buys on their computer, the sale disappears from the GA4 report, but it can show up in yours if checkout stores the link code. To send the sale back to Meta, see the Conversions API for WhatsApp.

How to do this with Vectalk

In Vectalk you create the template with a dynamic URL button through the API (create template), with components in the Cloud API format shown above, and send it with the button parameter through the messages endpoint (send template, “Botão com parâmetro dinâmico” example). Status webhooks arrive at the endpoint you register, ready to feed the sends table.

The redirector, GA4 and the report live in your system: Vectalk has no campaign tool or URL shortener in its dashboard. Each delivered message is deducted from your prepaid balance, with no card registered with Meta. Create your account at apps.vectalk.com.br/register. If you’re just getting started, read the official WhatsApp API guide.

Frequently asked questions

Can I put the variable in the middle of the URL?

No. Meta accepts at most one variable in the button URL, and it is always appended to the end of the address.

Can I use a third-party shortened link in the button?

The components documentation doesn’t mention shorteners. We recommend a redirect on your own domain: the customer recognizes the address, and you control the destination and log the click.

Does it work in utility templates?

Yes, the dynamic URL button exists in utility templates, for example as an order tracking link. Use a different utm_medium (such as utility) so it doesn’t mix with campaigns. Meta also counts clicks from utility templates in analytics.

Why does GA4 show fewer clicks than my redirector?

Blockers, cookie consent, slow-loading pages and repeated clicks make the numbers diverge. The redirector counts the request; GA4 counts the session that loaded the tag.

How do I track ads that lead to WhatsApp?

That’s a different path: click-to-WhatsApp ads carry source data in the customer’s first message. See how to track Click-to-WhatsApp ads.


Official sources consulted on October 29, 2026: Template components, WhatsApp Business Platform analytics, Pricing, UTM parameters in Google Analytics, GA4 default channel group, Best practices to avoid sending PII to Analytics, Vectalk documentation: send template.

Put your business on the official WhatsApp

Create your account, connect your number to the official API (you can keep the app with coexistence) and start serving customers and sending messages.