Tracking and events
One link goes out from your page to ours. One postback comes back to your server when a lead sells. This page shows the shape of both, what goes in each field, how to pass the sale to Meta, and a short test to run before you spend.
The link shape
You get your own tracking link when you sign up. It always has this shape. The capitals are placeholders you replace.
https://TRACKING-DOMAIN/AFFCODE/OFFERCODE/?sub1=SOURCE&sub2=YOUR_CLICK_ID&sub3=CREATIVE&street=123+Main+St&city=Denver&state=CO&zip=80202
- TRACKING-DOMAIN
- The tracking domain on the link we give you.
- AFFCODE
- The first part of the path. It identifies you, the partner.
- OFFERCODE
- The second part of the path. It identifies the offer, and so the traffic type.
- sub1, sub2, sub3
- Your three tracking fields. Their meaning is fixed, see below.
- street, city, state, zip
- The confirmed address, added by your page at the hand-off. See the address fields.
Use the link exactly as we give it to you. Do not swap the two path parts or drop the trailing slash.
The three sub IDs
Every partner uses the same meaning, so we can read your traffic the same way you do.
- sub1
- The source, or your sub-affiliate. A short fixed word per traffic type.
- sub2
- Your own click ID. It comes back to you on the sold-lead postback, so it must be unique per click.
- sub3
- The creative: the ad, the message or the drop that brought the click.
The three sub IDs for each traffic type
| Traffic | sub1 | sub2 | sub3 |
|---|---|---|---|
| Meta and paid social | meta | Your click ID, mapped on your server to Meta's click data | The ad ID |
| Text (SMS) | sms | Your click ID | The list or message code |
email | Your click ID | The creative or the drop | |
| Path and survey | path | Your click ID | The publisher or placement in your path |
| Search | search | Your click ID | The ad or ad group |
| Native and display | native | Your click ID | The ad or widget creative |
sub1 or the publisher in sub3. Tell us your mapping before launch and we will read it your way. Never move your click ID out of sub2: it is the field that comes back on the postback.The address fields you add
At the hand-off, add the confirmed property address to your tracking link. Our page opens with it already filled in. Try it here.
What each field means, and how to encode it
- street
- Street number and name, as the visitor confirmed it.
- city
- City.
- state
- Two letters, for example
CO. - zip
- Five digits.
- lat, lng
- Optional. The coordinates from your address lookup. They help us show the right house picture.
Encode the values properly (a space becomes + or %20). Building the link with the browser's own URL tools does this for you; the code is on the blueprint page.
Why your click IDs must survive
Your sub2 is the only thing that ties a sale back to a click.
When a lead sells, the postback carries your sub2 and nothing that identifies the visitor. If sub2 is empty, repeated, or lost on the way, the sale still happens but you cannot tell which ad earned it, and you cannot tell Meta.
Things that break it:
- A redirect, link shortener or page builder that drops the query string.
- Reusing one click ID for many visitors, or putting the campaign name in
sub2. - Building the hand-off link from scratch and forgetting to copy the sub IDs across.
- Not saving Meta's click data (the
fbclidin the URL, and the_fbcand_fbpcookies) next to your click ID when the visitor first arrives.
The sold-lead postback
When a lead you sent sells, a conversion is recorded against your click. Your tracking account then calls the postback URL you set, server to server, with your sub2 and the payout.
Add a postback URL
In your tracking account, add a postback URL that points at your own server.
Map the two macros
Put the account's macro for
sub2in one parameter and its macro for the payout in another. Your account lists the exact macro names.Pass the sale on
On your server, look up the click by
sub2, ignore repeats, and pass the sale on to Meta.
https://YOUR-SERVER.example/postback?click_id=SUB2_MACRO&payout=PAYOUT_MACRO
When the sale arrives
The sale can arrive some time after the visit, not at the moment of the hand-off, because we verify the lead before it is offered to buyers. Not every lead sells, so not every click gets a postback. The receiver itself is part 7 on the blueprint page.
Feeding the sale to Meta
Send the sale to Meta from your server through the Conversions API, as its own event, matched to the click you logged.
| Tactic | How sure | What we know |
|---|---|---|
| Optimize on the sold-lead signal, not Meta's lead count | Proven in our data one ad | On one ad, the lead count Meta reported was far higher than the real leads our system recorded. Judge ads on sales. |
| Send raw-lead and sold-lead signals as separate events | From our experience | Keep a hand-off and a sale as two different events so Meta never mixes them up. Your own handoff event is your raw-lead signal. |
| Deduplicate with an event ID | Untested | Give each sold-lead event a stable ID built from your click ID. If a postback repeats, or you also fire the same event from the browser, Meta counts it once. |
| Pass the payout as the event value | Untested | Lets Meta weigh sales by what they earned you. We have no result on whether it helps delivery. |
An example sold-lead event, built from your click log. Fill the values from the click record, never from the visitor's contact details, which you do not have.
{
"data": [{
"event_name": "sold_lead",
"event_time": 1792681512,
"event_id": "c_8f2k1x-sold",
"action_source": "website",
"event_source_url": "https://yourbrand.com/check",
"user_data": {
"fbc": "VALUE_OF__fbc_COOKIE",
"fbp": "VALUE_OF__fbp_COOKIE",
"client_ip_address": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 (iPhone; ...)"
},
"custom_data": { "value": 0.00, "currency": "USD" }
}]
}
The sending function, and testing it
Put the payout from the postback in value. While you test, use Meta's Test Events tool so test sales stay out of your real data.
JavaScript (Node)
// Node 18 or newer: built-in fetch, nothing to install.
var META_PIXEL_ID = process.env.META_PIXEL_ID;
var META_ACCESS_TOKEN = process.env.META_ACCESS_TOKEN; // keep it on the server
var META_API_VERSION = process.env.META_API_VERSION; // the Graph API version Meta lists as current
var META_TEST_EVENT_CODE = process.env.META_TEST_EVENT_CODE; // set while you test, from Test Events
// One sold-lead event, filled from the click you logged, never from contact details.
function sendServerEvent(eventName, click, value) {
var payload = {
data: [{
event_name: eventName,
event_time: Math.floor(Date.now() / 1000),
event_id: click.click_id + '-sold',
action_source: 'website',
event_source_url: process.env.PAGE_URL, // your pre-lander, for example https://yourbrand.com/check
user_data: {
fbc: click.fbc,
fbp: click.fbp,
client_ip_address: click.ip,
client_user_agent: click.user_agent
},
custom_data: { value: value, currency: 'USD' }
}]
};
if (META_TEST_EVENT_CODE) payload.test_event_code = META_TEST_EVENT_CODE;
var url = 'https://graph.facebook.com/' + META_API_VERSION + '/' + META_PIXEL_ID +
'/events?access_token=' + encodeURIComponent(META_ACCESS_TOKEN);
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}).then(function (res) {
if (!res.ok) throw new Error('Meta answered ' + res.status);
});
}Python
# Python 3 standard library only.
import json, os, time
from urllib.parse import urlencode
from urllib.request import Request, urlopen
META_PIXEL_ID = os.environ["META_PIXEL_ID"]
META_ACCESS_TOKEN = os.environ["META_ACCESS_TOKEN"] # keep it on the server
META_API_VERSION = os.environ["META_API_VERSION"] # the Graph API version Meta lists as current
META_TEST_EVENT_CODE = os.environ.get("META_TEST_EVENT_CODE") # set while you test, from Test Events
# One sold-lead event, filled from the click you logged, never from contact details.
def send_server_event(event_name, click, value):
payload = {"data": [{
"event_name": event_name,
"event_time": int(time.time()),
"event_id": click["click_id"] + "-sold",
"action_source": "website",
"event_source_url": os.environ.get("PAGE_URL"), # your pre-lander, for example https://yourbrand.com/check
"user_data": {
"fbc": click.get("fbc"),
"fbp": click.get("fbp"),
"client_ip_address": click.get("ip"),
"client_user_agent": click.get("user_agent"),
},
"custom_data": {"value": value, "currency": "USD"},
}]}
if META_TEST_EVENT_CODE:
payload["test_event_code"] = META_TEST_EVENT_CODE
url = "https://graph.facebook.com/%s/%s/events?%s" % (
META_API_VERSION, META_PIXEL_ID, urlencode({"access_token": META_ACCESS_TOKEN}))
req = Request(url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"})
with urlopen(req, timeout=10) as r: # raises an error unless Meta answers with success
r.read()Plain steps
send_server_event(name, click, value):
event = the example event, filled from the click record:
event_name = name, event_time = now in seconds, event_id = click.click_id + "-sold"
user_data = click.fbc, click.fbp, click.ip, click.user_agent
custom_data = value and currency
POST { "data": [event] } to the Meta Conversions API for your pixel, with your access token
while you test, add your Test Events code so test sales stay out of your real dataEvent names
Fire the first four from your page and log them on your server. The fifth comes back to you. The names are our recommendation Untested.
- prelander_view
- Your pre-lander loads. Fired by you.
- qualified
- The visitor passes your qualifying questions. Fired by you.
- address_confirmed
- The visitor taps "Yes, this is my home" and passes your bot filter. Fired by you.
- handoff
- Your page sends the visitor to ours. Fired by you. This is your raw-lead signal.
- sold_lead
- The lead sold. Arrives on your postback; your server sends it to Meta.
Fire your events on your own pages and from your own server only. Partner pixels and tags are not allowed on our pages.
Test it before you spend
Fire one test link, check the address arrives pre-filled, check your click ID comes back.
Build one test link You
Use a made-up click ID you will recognise, such as
test-0001, and a covered address that is not a real seller's home. Your office works.Open it on a phone, on mobile data You
Go through your own pre-lander to the confirm tap. Check your logs show one click and the four events, in order, all with
test-0001.Check our page SellHouseFast
Our page should open with street, city, state and ZIP already filled in, and the right house picture if you sent
latandlng. Stop there. Do not submit contact details for a test.Ask us for a test sale You
Send us your test click ID. We can record a test conversion against it so you can watch the postback arrive.
Check your click ID comes back You
Your server should receive the postback with
test-0001and a payout field, send one sold-lead event to Meta's Test Events tool, and ignore the postback if it arrives twice.
https://TRACKING-DOMAIN/AFFCODE/OFFERCODE/?sub1=meta&sub2=test-0001&sub3=test&street=1+Example+Way&city=Denver&state=CO&zip=80202
Something missing? Send us the exact link you used and the time you opened it. Then see how to get set up.