
# Ad click attribution for WhatsApp

If you run Meta or Google ads to a page on your own website, and that page has a WhatsApp button, <span data-t="appName">Your AI Connector</span> can tell you which ad click each WhatsApp conversation came from. You paste one small script on your website. When a visitor taps your WhatsApp button, the click is remembered and matched to the first message they send you. The contact then shows the campaign it came from, and the click data travels with the contact into webhooks, the API and, if you want, back to Meta.

This works for any page with a WhatsApp button: a landing page, a booking page, a price page or a quiz that ends in "Send my answers on WhatsApp".

> This page is about ads that send people to **your website** first. If your ads open WhatsApp directly (Meta's "Click to WhatsApp" ads), that is covered automatically, without a script, in [Click-to-WhatsApp Ad Attribution](click-to-whatsapp-attribution.md).

---

## Set it up

1. Go to **Settings → Ad click tracking** (Integrations group).
2. Click **Turn on and get my script**. Your account gets its own site key and a one line script.
3. Copy the script and paste it just before the closing `</body>` tag on every page that has a WhatsApp button:

{% code overflow="wrap" %}
```html
<script src="https://api.youraiconnector.com/v1/ad-visit/YOUR_SITE_KEY.js" async></script>
```
{% endcode %}

4. Optional: under **WhatsApp number**, type the number your buttons link to. Only links to that number are then tracked. Leave it empty to track every WhatsApp link on the site.
5. Choose the wording of the enquiry number (see below) and click **Save**. You do not need to change the script on your website after a settings change.
6. Test it: open your page from a link with a campaign tag on it, for example `https://yoursite.com/?utm_source=test&utm_campaign=setup-check`, tap your own WhatsApp button and send the message. Open that contact in Chats: the contact panel shows **Came from an ad click** with the campaign.

The site key is already inside the script. It is not a password, so it is fine on a public page.


---

## What the script does

- **When the page loads**, it keeps the ad click ids and campaign tags (the `utm_` parameters) from the page address in the browser tab's session storage, so they are still there if the visitor looks at another page first. It sets no cookie, loads nothing else and sends nothing.
- **When the visitor taps a WhatsApp link** (`wa.me`, `api.whatsapp.com/send` or `whatsapp://send`), it adds the enquiry number to the message, records the visit and lets the tap continue to WhatsApp as normal.

Nothing is sent to <span data-t="appName">Your AI Connector</span> before the tap.

---

## The enquiry number

A short number, like the reference a shop or clinic gives you, is added to the visitor's WhatsApp message. It is how <span data-t="appName">Your AI Connector</span> knows exactly which ad click belongs to which conversation. A message looks like this:

```
Hi, I would like to know more.
Enquiry no. 4827
```

- **Wording:** "Enquiry no." is the default. You can use any one line of text that sounds natural for your business, such as "Booking ref" or "Ref". No digits.
- **Where:** **On its own last line** (the default) or **At the end of the text**. The settings page shows a live preview.
- **Length:** 4 digits, or 5 when your site is very busy.
- **Your AI agent never sees it** and never repeats it. The inbox shows the full message, number included.

You can switch the enquiry number off. Matching then only works on the text of the message, so two visitors who send exactly the same text (for example the same quiz answers) cannot be told apart, and some clicks will not be matched.

---

## Your own quiz or button code

If your page builds the WhatsApp message itself (a quiz that puts the answers in the text, or your own button code), add `data-manual` to the script tag:

{% code overflow="wrap" %}
```html
<script src="https://api.youraiconnector.com/v1/ad-visit/YOUR_SITE_KEY.js" data-manual async></script>
```
{% endcode %}

With `data-manual` the script no longer changes WhatsApp links by itself. Your own code calls it right before it opens WhatsApp, so each visit is recorded once:

- `window.ChatAttribution.text(finalText, number)` returns your text with the enquiry number added, and records the visit. Use the returned text in your WhatsApp link.
- `window.ChatAttribution.link(finalText, number)` does the same and returns a ready `wa.me` link with that text. Leave out the number to use the one under **WhatsApp number** in the settings.

{% code overflow="wrap" %}
```html
<script>
  function sendAnswers(answers) {
    var text = "Hi, my answers: " + answers.join(", ");
    if (window.ChatAttribution) {
      window.location.href = window.ChatAttribution.link(text, "+15551234567");
    } else {
      window.location.href = "https://wa.me/15551234567?text=" + encodeURIComponent(text);
    }
  }
</script>
```
{% endcode %}

The `else` branch keeps the button working if the script has not loaded, for example when a browser extension blocks it. A text that already carries a number the script issued is never changed a second time.

---

## What gets recorded

At the tap, the visit record holds:

- the ad click ids in the page address, such as `fbclid` (Meta) and `gclid` (Google)
- the `_fbc` and `_fbp` cookies, if your own Meta pixel set them on your site (the script reads them, it never sets them)
- the campaign tags (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`)
- the landing page, the page the visitor came from, the browser, and the message text
- the IP address and the time, added by <span data-t="appName">Your AI Connector</span> when the visit arrives

When a visit is matched, <span data-t="appName">Your AI Connector</span> also works out from that data roughly where the visitor was (city and country, approximate, from the IP address) and which device they used (phone, tablet or computer, the operating system and the browser, for example the Instagram in-app browser). Location stays empty when it cannot be worked out, which is common on mobile networks.

Visit records are kept for **90 days**. When a visit is matched, a copy of its click data is stored on the contact and stays with the contact until you delete it.

**Your privacy notice:** the script runs on your website, so your website's privacy notice should mention it: what it records (the list above) and that it does so when a visitor taps a WhatsApp button.

---

## How matching works

When a contact's first message arrives, <span data-t="appName">Your AI Connector</span> looks for a visit from the **15 minutes** before it:

1. **By enquiry number first.** A message carrying a number the script issued is matched to that visit.
2. **Otherwise by identical text.** A message with exactly the same text as one visit is matched to it. When two different visitors sent the same text inside the window, neither is matched, on purpose, so a contact is never credited to the wrong ad.

A later message that carries the enquiry number is also matched, as long as the contact has no match yet.

When a contact who already has a match comes back through another ad and sends a message carrying a newer enquiry number, that click is kept as the contact's **latest click**. The first match stays as it is.

- **First click, for your reporting:** the contact keeps showing where the lead first came from. That never changes.
- **Latest click, for Meta:** events sent to Meta use the newest click, because Meta credits the ad that brought the person back and only inside its attribution window. Reporting an old click would credit the wrong ad, or none at all.
- The latest click is only taken from an enquiry number, never from matching text alone, and an older visit never replaces a newer one.

Limits:

- **The first match is never overwritten.** Newer clicks go into the latest click.
- **WhatsApp only:** the WhatsApp Business API and WhatsApp Web channels.
- **15 minutes:** a visitor who taps the button but sends the message much later is not matched.
- **Going forward only:** contacts from before you installed the script have no match.

---

## Where you see it

- **Contact panel:** **Came from an ad click** with the campaign or source and the date of the first click. Hover for the details: the campaign, the **Ad** (`utm_content`) and **Ad set** (`utm_term`), the approximate **Location** and the **Device**, each only when it is known, plus the same for the latest click when there is one. Campaign tags that were never filled in (an ad preview tap sends the tag template instead of the names) are left out. A visit only counts as an ad click when the page was opened from an ad link (a Meta or Google click id, or campaign tags). Anyone who reached the page another way, for example from Google search, by typing your address or by clicking through your site, shows **Came from your website** with where they came from, such as **Google search**, or **Website visit, no referrer recorded** when the browser did not say.
- **Contact page:** the same line sits under the contact's name in **Contacts**, so you see it straight away, on your phone too. Hover or tap it for the details.
- **Activity:** the contact's timeline starts with **Tapped your WhatsApp button after an ad** (or **on your website**) at the time of the click, before their first message. The chat's contact panel shows the same line under **Activity**.
- **Webhooks:** `contact.ad_attribution` in [agent tag webhooks](webhooks.md#contact-tags-updated-webhook) and in every webhook subscription event that carries a contact.
- **API:** `ad_attribution` on the contact list and `adAttribution` on a single contact, in the Contacts section of the API reference.
- **MCP tools:** the same object on every contact they return.

The value is `null` when the contact has no match. The fields below describe the first click. When a later click was matched, `latest` holds it with the same fields; it is absent until then.

| Field | What it is |
|---|---|
| `visit_at` | When the visitor tapped the WhatsApp button. |
| `matched_at` | When the visit was matched to the contact. |
| `match_method` | `reference` (matched by enquiry number) or `text` (matched by identical message text). |
| `reference` | The enquiry number, or `null` when it was off. |
| `fbc` / `fbp` | The Meta click and browser cookies from your own Meta pixel, if present. |
| `fbclid` | The Meta ad click id from the page address. |
| `gclid` / `gbraid` / `wbraid` | The Google ad click ids from the page address. |
| `utm_source` / `utm_medium` / `utm_campaign` / `utm_content` / `utm_term` | The campaign tags from the page address. |
| `landing_url` | The page the visitor landed on. |
| `referrer` | The page the visitor came from. |
| `client_ip` | The visitor's IP address. |
| `user_agent` | The visitor's browser. |
| `geo_country` / `geo_region` / `geo_city` / `geo_timezone` | Approximate location from the IP address: two letter country code, region code, city and time zone. Absent when unknown. |
| `device_type` / `device_os` / `device_browser` | The device from the browser: `mobile`, `tablet` or `desktop`, the operating system with its version (such as `iOS 18.3`) and the browser (such as `Safari` or `Instagram in-app`). Absent when unknown. |
| `site_key` | The site key of the script that recorded the visit. |
| `visit_id` | The id of the visit record. |
| `latest` | The newest click matched after the first one, with the same fields. Used for events sent to Meta. Absent when there is none. |

---

## Send results to Meta (optional)

You can let <span data-t="appName">Your AI Connector</span> report outcomes back to your Meta ad account, so Meta learns which ads bring real leads and sales and optimises toward them. This uses your own Meta Conversions API details.

1. In Meta Events Manager, find your pixel (also called dataset) ID under Data sources, and create an access token under Settings, Conversions API, **Generate access token**.
2. In **Settings → Ad click tracking**, under **Send results to Meta (optional)**, enter the **Pixel / dataset ID** and the **Access token**. Click **Save Meta details**.
3. Optional: check the connection. In Meta Events Manager, open your pixel and go to the **Test events** tab, copy the test code shown there, paste it under **Send a test event** and click **Send test event**. The line below the button says "Meta received 1 test event" (look for it under Test events in Meta), or shows Meta's reason when it was refused, for example a wrong token. The code is used for that one test only and is never saved.
4. Open the agent editor, go to the **Tags** tab and open a tag. Under **Send to Meta**, choose the event: **Lead**, **Contact**, **Schedule**, **Purchase** or a custom name. Optionally add a value and a currency.

From then on, whenever that tag is applied to a contact (by the AI, by hand, through the API or by an automation), <span data-t="appName">Your AI Connector</span> sends the event to Meta with the contact's newest click data (`fbc`, `fbp`, IP address, browser, landing page, from the latest click when there is one, else the first) plus their phone, email and name, hashed, so Meta can link it to the ad. A contact without a match still sends the event, with only the hashed phone, email and name.

- **Live events always count:** a test event code is never stored with your Meta details, so the events your tags and automations send always go to your real data in Meta, never to the Test events tab.
- **No double counting:** each event has a fixed id made of the contact id and the tag name. If your CRM sends the same event to Meta with the same id, Meta keeps only one.
- **Meta's attribution window:** Meta only credits an event to an ad inside its attribution window, which is 7 days after the click by default. A deal that closes weeks later will not count on its own, so also send an earlier event, for example a tag for the booked or paid consultation.

---

## Send conversions from automations

A tag sends its event when the tag is applied. When the result happens somewhere else, such as a deal closing in your CRM or a consult being booked, use the **Send conversion to Meta** action in an automation. It reports one event to your Meta dataset through the Conversions API, so Meta optimises your ads on real results and not only on messages.

It uses the same Meta details as the tags: the **Pixel / dataset ID** and **Access token** under **Settings → Ad click tracking**. Without them the step fails with "Meta is not connected".

**The fields**

| Field | What it does |
|---|---|
| **Event name** | Required. **Purchase**, **Schedule**, **Lead** or your own name. Your own name uses letters, digits, spaces and underscores, starts with a letter and is at most 40 characters. |
| **Value** and **Currency** | The amount of the conversion, such as the deal amount. Currency is a 3 letter code and defaults to EUR. The value is only sent when you fill it in. |
| **Contact** | Optional. The contact the conversion belongs to. |
| **Email**, **Phone**, **First name**, **Last name** | Details of the person. Fill these in when the person is not a contact, or to add to what the contact has. |
| **Your own ID for the person** | An id for the person from your own system, such as a CRM id. |
| **Event ID** | The id Meta uses to count the event once. See below. |
| **Event time** | When the conversion happened, as unix seconds or a date. At most 7 days ago. Leave it empty to use the moment the step runs. |
| **Page URL** | The page the conversion is linked to, if any. |
| **Test event code** | Only for a test run: the code from Meta's Test events tab. An event sent with it shows up under Test events and does not count for your ads. Leave it empty for live events. |

Fill in at least one of **Contact**, **Email** or **Phone**. The person does not have to be a contact: a HubSpot deal that reaches Closed Won can send a Purchase with the deal amount and the buyer's email.

When you pick a **Contact**, their phone, email and name are used. If they came from an ad click that the script matched, the click details go along too, which helps Meta match the event to the ad. Email, phone and names are hashed before they leave. The click id, IP address and browser are sent as Meta requires.

**Event ID and double counting**

Meta counts events with the same event name and the same Event ID once. Use a fixed id, such as the deal id, when another tool also reports the same conversion, or when the automation might run twice for it. Leave it empty and a new id is made on every run, so every run counts.

**How it differs from tag events**

A tag's **Send to Meta** event goes out once per contact for that tag (at most once in 30 days). This action sends every time it runs. The Event ID is what stops a conversion being counted twice.

**Example: a closed deal and a booked consult**

1. Start an automation that runs when a deal is won: an **App event** trigger on your CRM's deal event, with a **Condition** so only won deals go on. HubSpot only sends deal-stage events to developer apps, so for HubSpot use a **Schedule** trigger with a HubSpot search for deals that reached Closed Won since the last run, and a **Loop** over them.
2. Add **Send conversion to Meta**. Set **Event name** to **Purchase**, **Value** to the deal amount (insert it with the **+** button) and **Currency** to your currency. Put the buyer's email in **Email** and the deal id in **Event ID**, so the same deal is never counted twice.
3. For a booked consult, start a second automation with the **Appointment booked** trigger and add **Send conversion to Meta** with **Event name** set to **Schedule** and the contact from the trigger in **Contact**. If you want, add a second **Send conversion to Meta** step that sends a **Purchase** with a fixed value, such as the price of the consult.

You can also send a conversion over the API with `POST https://api.youraiconnector.com/v1/ad-attribution/meta-event`. It takes the same fields in snake case: `event_name`, `value`, `currency`, `contact_id`, `email`, `phone`, `first_name`, `last_name`, `external_id`, `event_id`, `event_time`, `event_source_url` and `test_event_code`.

---

## FAQ

**Does this work if my ad opens WhatsApp directly?**
Those ads do not pass through your website, so the script never runs. Meta sends the ad details with the first message instead, see [Click-to-WhatsApp Ad Attribution](click-to-whatsapp-attribution.md).

**Does the script slow down my website or set cookies?**
It loads in the background (`async`), sets no cookie and loads nothing else. It only sends something when a visitor taps a WhatsApp button.

**Can I track Google Ads as well as Meta ads?**
Yes. Google click ids (`gclid`, `gbraid`, `wbraid`) and any campaign tags are recorded and shown on the contact. Sending results back is available for Meta only.

**Why did a contact not get a match?**
The most common reasons: the message arrived more than 15 minutes after the tap, the enquiry number was removed from the message, the enquiry number is off and another visitor sent the same text, or the page does not have the script.

**My website links to two WhatsApp numbers. Can I track only one?**
Yes. Type that number under **WhatsApp number** in the settings. Leave it empty to track every WhatsApp link.

**Can I change the wording later?**
Yes. Changes apply to your website straight away; you do not need to paste the script again.
