Conversion tracking
How to fire a postback when a user converts on your site - required for CPA campaigns to attribute and bill correctly.
For CPA campaigns, we only know a user converted when you tell us. The mechanism is a postback: when a user does the thing you want them to do on your site, your server fires a quick request to ours, and we record the conversion against the original click. Without this wiring, your CPA campaign will serve clicks but never bill - because we have no way to know any of them turned into customers.
This page walks through the wiring. About 15 minutes of dev work, no SDK required.
The flow
- A user clicks your ad on a publisher's site.
- Our ad server adds
?efaid=<click_id>to your destination URL and redirects them there. The click ID is a 24-character hex string unique to this click. - Your landing page captures the
efaidvalue (URL param) and stores it - usually in a session cookie that survives across page loads. - When the user completes whatever counts as a conversion (signup, purchase, install), your server fires a postback to
https://ads.efind.com/serve/postback.php?efaid=<click_id>. - We look up the click, record the conversion, and bill you at the bid rate that was in effect when the click happened.
Step 1: Capture efaid on your landing page
The URL in the user's browser when they land on your site will look like:
https://example.com/landing?efaid=ee40d35706798218cce42929
Grab the efaid parameter and store it. Cookies are the most common choice because they survive multi-step funnels. Example in JavaScript:
const url = new URL(window.location);
const efaid = url.searchParams.get('efaid');
if (efaid) {
document.cookie = `efaid=${efaid}; path=/; max-age=${30*24*60*60}; SameSite=Lax`;
}
Or PHP, if you control the landing page server-side:
<?php
if (!empty($_GET['efaid']) && preg_match('/^[a-f0-9]{24}$/', $_GET['efaid'])) {
setcookie('efaid', $_GET['efaid'], time() + 30*24*60*60, '/', '', true, true);
}
efaid survives only as long as your storage. A user who clears cookies between landing and converting will be unattributable. Most platforms see ~5-10% leakage from this - it's the cost of doing business across the open web.Step 2: Fire the postback on conversion
The instant a conversion happens server-side, fire a single GET to our postback URL with the stored efaid:
PHP
$efaid = $_COOKIE['efaid'] ?? '';
if ($efaid !== '') {
$url = 'https://ads.efind.com/serve/postback.php?efaid=' . urlencode($efaid);
// Fire and forget - don't block the user's request on this.
@file_get_contents($url, false, stream_context_create([
'http' => ['timeout' => 2, 'ignore_errors' => true]
]));
}
Node.js
const efaid = req.cookies.efaid;
if (efaid) {
fetch(`https://ads.efind.com/serve/postback.php?efaid=${encodeURIComponent(efaid)}`)
.catch(() => {}); // fire-and-forget
}
Python
import requests
efaid = request.COOKIES.get('efaid')
if efaid:
try:
requests.get(f'https://ads.efind.com/serve/postback.php?efaid={efaid}', timeout=2)
except requests.RequestException:
pass
cURL (for testing or shell-based stacks)
curl -s "https://ads.efind.com/serve/postback.php?efaid=$EFAID"
Alternative: pixel-based (no server access)
If you can't fire a server-side postback - common on SaaS landing pages or simple thank-you pages - you can use a 1×1 image pixel instead. Drop this into the HTML of your conversion confirmation page (Shopify "thank you" page, Wix order confirmation, etc.):
<img src="https://ads.efind.com/serve/pixel.php?efaid=YOUR_EFAID_HERE"
width="1" height="1" alt="" style="display:none">
You'll need to template the YOUR_EFAID_HERE with the stored value - most e-commerce platforms have a way to inject custom HTML with order metadata. Pixels are slightly less reliable than server postbacks (ad blockers, browsers blocking third-party requests) but they're easier to set up.
Response format
postback.php returns JSON. pixel.php returns the image but adds debug info in headers - useful for verifying the integration with your browser's dev tools.
JSON shape
{ "ok": true, "recorded": true, "reason": null } ← conversion logged ✓
{ "ok": true, "recorded": false, "reason": "already_recorded" } ← duplicate fire (no harm)
{ "ok": true, "recorded": false, "reason": "not_found" } ← efaid doesn't match a click
{ "ok": true, "recorded": false, "reason": "bad_click_id" } ← malformed efaid
{ "ok": true, "recorded": false, "reason": "window_expired" } ← click was over 30 days old
HTTP status is always 200 regardless. The work-or-not signal is in recorded. Your integration should treat recorded: true as success and any other state as "fired but didn't take" - which is usually fine (most "no" reasons are operationally normal).
Testing your integration
- Set up a test campaign in the dashboard (any bid - you won't actually be charged for test conversions on draft campaigns).
- From a publisher's site or directly via our ad server URL, click on an ad and note the
efaidappended to your landing URL. - Trigger your conversion flow in a way that fires the postback.
- Check the campaign detail page in your dashboard - the Conversions stat should tick up within a minute.
- Fire the same postback again with the same
efaid- the stat should NOT tick up a second time (idempotent).
Important behaviors
- Idempotent. Firing the same postback twice (network retries, dev mistakes) is safe - only the first one counts.
- Bid is snapshotted at click time. If you change your bid after a click but before the conversion postback, we use the bid that was in effect when the click happened. This keeps publisher economics stable.
- 30-day window. Postbacks for clicks older than 30 days are silently ignored. If you have an unusually long conversion funnel, talk to us - we can bump the window per-advertiser.
- Bot clicks don't get an efaid. Our dedup layer filters obvious refresh-spam and naive click bots at click time; those redirects don't include an
efaid, so there's nothing to postback against. This protects your spend.