A postback URL is a server-to-server callback that fires when a conversion event occurs. When a user installs your app, your attribution system sends an HTTP request to the ad network's postback URL with the install details: campaign ID, device identifier, timestamp, and any custom data. The ad network uses this data to optimize its campaigns and report conversions.
This guide covers how postback URLs work in mobile attribution. For the install attribution flow, see install attribution flow: from ad click to first open. For webhook integrations, see webhooks and integrations for deep linking.
How Postback URLs Work
The Flow
1. User clicks ad on Ad Network A
2. Ad Network A records click with click_id=ABC123
3. User installs and opens app
4. Attribution provider determines Ad Network A drove the install
5. Attribution provider sends HTTP GET/POST to Ad Network A's postback URL:
https://postback.adnetwork-a.com/install?click_id=ABC123&app_id=com.yourapp&event=install
6. Ad Network A records the conversion and optimizes future ad delivery
Postback URL Structure
A typical postback URL uses macro placeholders that the attribution provider replaces with actual values:
https://postback.adnetwork.com/conversion?
click_id={click_id}&
app_id={app_id}&
event_name={event_name}&
event_time={event_time}&
device_id={device_id}&
revenue={revenue}&
currency={currency}
The macros (values in curly braces) are replaced at send time:
https://postback.adnetwork.com/conversion?
click_id=ABC123&
app_id=com.yourapp&
event_name=install&
event_time=1720780200&
device_id=hashed_gaid&
revenue=0&
currency=USD
Common Postback Macros
| Macro | Description | Example |
|---|---|---|
{click_id} |
Ad network's click identifier | ABC123 |
{app_id} |
App bundle ID / package name | com.yourapp |
{event_name} |
Conversion event name | install, purchase, signup |
{event_time} |
Event timestamp (Unix) | 1720780200 |
{device_id} |
Device advertising ID (hashed) | sha256_of_gaid |
{idfa} |
iOS Identifier for Advertisers | A1B2C3D4-... |
{gaid} |
Google Advertising ID | a1b2c3d4-... |
{revenue} |
Revenue amount | 9.99 |
{currency} |
Revenue currency | USD |
{campaign_id} |
Campaign identifier | CAMP-789 |
{ad_group_id} |
Ad group identifier | AG-456 |
{creative_id} |
Creative identifier | CR-123 |
{country} |
Install country (ISO 3166) | US |
{os_version} |
Device OS version | 17.5 |
{ip} |
User's IP address | 203.0.113.42 |
Types of Postbacks
Install Postback
Fires when the app is installed and opened for the first time:
https://postback.adnetwork.com/install?
click_id={click_id}&
event=install&
event_time={event_time}&
device_id={device_id}
In-App Event Postback
Fires when the user completes a specific in-app event (purchase, registration, level completion):
https://postback.adnetwork.com/event?
click_id={click_id}&
event={event_name}&
event_time={event_time}&
revenue={revenue}&
currency={currency}
Revenue Postback
A specialized in-app event postback that includes revenue data for ROAS (Return on Ad Spend) optimization:
https://postback.adnetwork.com/revenue?
click_id={click_id}&
event=purchase&
revenue=49.99&
currency=USD&
product_id=premium_annual
Rejection Postback
Fires when an install claim is rejected (fraud, duplicate, wrong attribution):
https://postback.adnetwork.com/reject?
click_id={click_id}&
rejection_reason=fraud_detected&
event_time={event_time}
Configuring Postback URLs
Setting Up a Postback
Each ad network provides its own postback URL template. You configure this in your attribution provider's dashboard:
{
"network": "Ad Network A",
"postback_url": "https://postback.adnetwork-a.com/conversion?click_id={click_id}&event={event_name}&revenue={revenue}",
"events": ["install", "purchase", "registration"],
"send_for": "attributed_only",
"method": "GET"
}
Event Mapping
Map your in-app events to the ad network's expected event names:
| Your Event | Ad Network Event | Postback |
|---|---|---|
first_open |
install |
Install postback |
registration_complete |
registration |
Event postback |
first_purchase |
purchase |
Revenue postback |
subscription_start |
subscribe |
Revenue postback |
tutorial_complete |
tutorial |
Event postback |
Building Your Own Postback System
If you are building attribution in-house, here is how to implement postback sending:
interface PostbackConfig {
networkId: string;
urlTemplate: string;
events: string[];
method: 'GET' | 'POST';
retryPolicy: { maxRetries: number; backoffMs: number };
}
async function sendPostback(
config: PostbackConfig,
eventData: Record<string, string>
): Promise<void> {
// Replace macros in URL template
let url = config.urlTemplate;
for (const [key, value] of Object.entries(eventData)) {
url = url.replace(`{${key}}`, encodeURIComponent(value));
}
// Remove any unreplaced macros
url = url.replace(/\{[^}]+\}/g, '');
for (let attempt = 0; attempt <= config.retryPolicy.maxRetries; attempt++) {
try {
const response = await fetch(url, { method: config.method });
if (response.ok) {
console.log(`Postback sent to ${config.networkId}: ${response.status}`);
return;
}
if (response.status >= 500) {
// Server error, retry
await sleep(config.retryPolicy.backoffMs * Math.pow(2, attempt));
continue;
}
// Client error (4xx), don't retry
console.error(`Postback failed for ${config.networkId}: ${response.status}`);
return;
} catch (error) {
if (attempt < config.retryPolicy.maxRetries) {
await sleep(config.retryPolicy.backoffMs * Math.pow(2, attempt));
}
}
}
console.error(`Postback to ${config.networkId} failed after ${config.retryPolicy.maxRetries} retries`);
}
Postback Validation
Verifying Incoming Postbacks
If you receive postbacks (e.g., from ad networks reporting click data), validate them:
function validatePostback(request: Request, networkSecret: string): boolean {
// Check signature
const signature = request.headers.get('X-Signature');
const body = request.body;
const expectedSignature = hmacSHA256(body, networkSecret);
if (signature !== expectedSignature) {
return false;
}
// Check timestamp freshness (prevent replay attacks)
const timestamp = parseInt(request.url.searchParams.get('event_time') || '0');
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 3600) {
return false; // More than 1 hour old
}
return true;
}
Common Postback Failures
| Issue | Cause | Fix |
|---|---|---|
| 404 response | Wrong postback URL | Verify URL with ad network |
| Missing click_id | Macro not replaced | Check macro syntax matches provider's format |
| Duplicate postbacks | Retry logic without dedup | Add idempotency keys |
| Delayed postbacks | Queue backlog | Monitor queue depth, scale workers |
| Empty revenue | Revenue macro not mapped | Map revenue events correctly |
Privacy and Postbacks
ATT and IDFA
On iOS, if the user has not consented to tracking via ATT, the IDFA is unavailable. Postbacks must use alternative identifiers:
- SKAdNetwork postbacks: Apple sends postbacks directly to the ad network with campaign-level (not user-level) data.
- Hashed identifiers: Some providers use hashed email or phone as an alternative identifier.
- No identifier: Some postbacks omit device identifiers entirely and rely on the click_id alone.
GDPR
Under GDPR, sending user data (device IDs, IP addresses) to third-party ad networks requires a legal basis. Ensure your privacy policy covers this data sharing, and provide users with opt-out mechanisms.
Tolinku for Attribution Callbacks
Tolinku's webhooks can be used as postback URLs for deep link attribution events. When a user clicks a Tolinku deep link and converts, Tolinku can send a webhook to your server with the attribution data. Configure webhooks in the Tolinku dashboard.
For mobile attribution, see mobile attribution: a developer's guide. For the full install attribution flow, see install attribution flow: from ad click to first open.
Get deep linking tips in your inbox
One email per week. No spam.