Overview
Quora Ads supports both client-side (browser-based) Pixel tracking and server-side Conversions API. Understanding when to use each approach is critical for accurate attribution, privacy compliance, and measurement accuracy.
Client-Side Tracking
How It Works
- JavaScript Pixel loads in user's browser
- Events fire directly from browser to Quora servers
- Third-party cookies set by Quora domain
- Automatic user identification via browser
- Real-time event tracking
Implementation
<!-- Client-Side Quora Pixel -->
<script>
!function(q,e,v,n,t,s){if(q.qp) return; n=q.qp=function(){n.qp?n.qp.apply(n,arguments):n.queue.push(arguments);}; n.queue=[];t=document.createElement(e);t.async=!0;t.src=v; s=document.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t,s);}(window, 'script', 'https://a.quora.com/qevents.js');
qp('init', 'PIXEL_ID');
qp('track', 'ViewContent');
// Purchase event
qp('track', 'Purchase', {
'value': '99.99',
'currency': 'USD',
'order_id': 'ORDER_12345'
});
</script>
Advantages
- Easy implementation: Simple script tag
- Automatic tracking: Browser handles user identification
- Real-time data: Events appear immediately in dashboard
- Device data captured: User agent, IP automatically included
- No server infrastructure: Works standalone
- Dynamic remarketing: Product-level retargeting
Limitations
- Ad blockers: 20-40% of events may be blocked
- Third-party cookie restrictions: Safari ITP, Firefox ETP limit tracking
- Privacy regulations: GDPR/CCPA compliance required
- Client-side delays: Page load affects tracking
- Limited control: Cannot modify data post-firing
- Fraud vulnerability: Easier to spoof client-side
Server-Side Tracking (Conversions API)
How It Works
- Server makes API call to Quora Conversions endpoint
- No browser involvement - server-to-server
- User identification via hashed email or external ID
- Full control over data sent
- Privacy-preserving implementation
Implementation
Python Example
import requests
import hashlib
import time
def hash_email(email):
"""Hash email for privacy"""
return hashlib.sha256(email.lower().strip().encode()).hexdigest()
def track_quora_conversion_server(pixel_id, access_token, event_data):
"""Track conversion via Quora Conversions API"""
url = 'https://conversion-event.quora.com/conversion/event'
headers = {
'Content-Type': 'application/json'
}
payload = {
'pixel_id': pixel_id,
'access_token': access_token,
'event_name': event_data['event_name'],
'event_time': int(time.time()),
'user_data': {
'em': hash_email(event_data['email']),
'client_ip_address': event_data.get('ip_address'),
'client_user_agent': event_data.get('user_agent')
},
'custom_data': {
'value': event_data.get('value'),
'currency': event_data.get('currency', 'USD'),
'order_id': event_data.get('order_id')
}
}
try:
response = requests.post(url, json=payload, headers=headers, timeout=10)
if response.status_code == 200:
print(f"✓ Quora conversion tracked: {event_data['order_id']}")
return True
else:
print(f"✗ API error: {response.status_code} - {response.text}")
return False
except Exception as e:
print(f"✗ Failed to track conversion: {e}")
return False
# Usage
track_quora_conversion_server(
pixel_id='PIXEL_ID',
access_token='ACCESS_TOKEN',
event_data={
'event_name': 'Purchase',
'email': 'user@example.com',
'value': 99.99,
'currency': 'USD',
'order_id': 'ORDER_12345',
'ip_address': '192.168.1.1',
'user_agent': 'Mozilla/5.0...'
}
)
Node.js Example
const axios = require('axios');
const crypto = require('crypto');
function hashEmail(email) {
return crypto
.createHash('sha256')
.update(email.toLowerCase().trim())
.digest('hex');
}
async function trackQuoraConversionServer(pixelId, accessToken, eventData) {
const url = 'https://conversion-event.quora.com/conversion/event';
const payload = {
pixel_id: pixelId,
access_token: accessToken,
event_name: eventData.eventName,
event_time: Math.floor(Date.now() / 1000),
user_data: {
em: hashEmail(eventData.email),
client_ip_address: eventData.ipAddress,
client_user_agent: eventData.userAgent
},
custom_data: {
value: eventData.value,
currency: eventData.currency || 'USD',
order_id: eventData.orderId
}
};
try {
const response = await axios.post(url, payload, {
headers: { 'Content-Type': 'application/json' },
timeout: 10000
});
if (response.status === 200) {
console.log(`✓ Quora conversion tracked: ${eventData.orderId}`);
return true;
}
} catch (error) {
console.error(`✗ Failed to track conversion:`, error.message);
return false;
}
}
// Usage
trackQuoraConversionServer('PIXEL_ID', 'ACCESS_TOKEN', {
eventName: 'Purchase',
email: 'user@example.com',
value: 99.99,
currency: 'USD',
orderId: 'ORDER_12345',
ipAddress: '192.168.1.1',
userAgent: 'Mozilla/5.0...'
});
PHP Example
<?php
function hash_email($email) {
return hash('sha256', strtolower(trim($email)));
}
function track_quora_conversion_server($pixel_id, $access_token, $event_data) {
$url = 'https://conversion-event.quora.com/conversion/event';
$payload = [
'pixel_id' => $pixel_id,
'access_token' => $access_token,
'event_name' => $event_data['event_name'],
'event_time' => time(),
'user_data' => [
'em' => hash_email($event_data['email']),
'client_ip_address' => $event_data['ip_address'] ?? null,
'client_user_agent' => $event_data['user_agent'] ?? null
],
'custom_data' => [
'value' => $event_data['value'] ?? null,
'currency' => $event_data['currency'] ?? 'USD',
'order_id' => $event_data['order_id'] ?? null
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code === 200) {
error_log("✓ Quora conversion tracked: {$event_data['order_id']}");
return true;
} else {
error_log("✗ API error: $http_code");
return false;
}
}
// Usage
track_quora_conversion_server('PIXEL_ID', 'ACCESS_TOKEN', [
'event_name' => 'Purchase',
'email' => 'user@example.com',
'value' => 99.99,
'currency' => 'USD',
'order_id' => 'ORDER_12345',
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
]);
?>
Advantages
- Bypasses ad blockers: Server requests not blocked
- Privacy-friendly: No third-party cookies in browser
- Reliable tracking: Not affected by client-side issues
- Data control: Full control over data sent
- Fraud prevention: Harder to spoof server-side
- Offline conversions: Track phone orders, in-store sales
- Data enrichment: Add server-side data before sending
Limitations
- Implementation complexity: Requires backend development
- User identification: Must collect and hash email
- No automatic device data: Must capture and send manually
- API authentication: Requires access token management
- Delayed reporting: May take longer to appear in dashboard
- More infrastructure: Backend dependencies required
Hybrid Approach (Recommended)
Combine both methods for maximum coverage and reliability:
Implementation Strategy
- Client-side: Primary tracking for real-time attribution
- Server-side: Backup for critical conversions
- Deduplication: Use order ID to prevent double-counting
Hybrid Implementation
// Client-side: Fire pixel immediately
function trackConversionClientSide(orderData) {
qp('track', 'Purchase', {
'value': orderData.value,
'currency': 'USD',
'order_id': orderData.orderId
});
// Also send to server for backup
sendToServerForBackup(orderData);
}
function sendToServerForBackup(orderData) {
fetch('/api/quora/conversion/backup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId: orderData.orderId,
value: orderData.value,
currency: 'USD',
email: orderData.email,
timestamp: Date.now()
})
});
}
# Server-side: Backup conversion tracking
from flask import Flask, request
import time
app = Flask(__name__)
@app.route('/api/quora/conversion/backup', methods=['POST'])
def backup_conversion():
data = request.json
# Store in database
db.conversions.insert_one({
'order_id': data['order_id'],
'value': data['value'],
'email': data['email'],
'client_timestamp': data['timestamp'],
'server_timestamp': time.time(),
'api_fired': False
})
# Schedule server-side API call after 1 hour (for deduplication)
schedule_api_call.delay(data['order_id'], delay=3600)
return {'status': 'queued'}
def schedule_api_call(order_id, delay):
"""Fire Conversions API after delay if client-side didn't succeed"""
time.sleep(delay)
conversion = db.conversions.find_one({'order_id': order_id})
if not conversion.get('api_fired'):
# Client-side didn't fire - use server-side backup
track_quora_conversion_server(
pixel_id='PIXEL_ID',
access_token='ACCESS_TOKEN',
event_data={
'event_name': 'Purchase',
'email': conversion['email'],
'value': conversion['value'],
'currency': 'USD',
'order_id': order_id
}
)
db.conversions.update_one(
{'order_id': order_id},
{'$set': {'api_fired': True, 'method': 'server_side_backup'}}
)
User Identification
Client-Side: Automatic
// Quora Pixel handles automatically via cookies
qp('track', 'Purchase', {
'value': '99.99',
'currency': 'USD',
'order_id': 'ORDER_12345'
});
Server-Side: Hashed Email Required
# Must provide hashed email for attribution
track_quora_conversion_server(
pixel_id='PIXEL_ID',
access_token='ACCESS_TOKEN',
event_data={
'event_name': 'Purchase',
'email': 'user@example.com', # Will be hashed
'value': 99.99,
'order_id': 'ORDER_12345'
}
)
Use Case Recommendations
E-commerce
Recommended: Hybrid approach
- Client-side: Real-time purchase tracking
- Server-side: High-value orders, returns/refunds
Lead Generation
Recommended: Client-side primary
Subscription Services
Recommended: Server-side
- Server-side: Subscription events, renewals
- Scheduled: Recurring revenue tracking
Mobile Apps
Recommended: Server-side via MMP
- Use mobile measurement partner (AppsFlyer, Adjust)
- Server-side Conversions API for purchases
Offline Sales
Recommended: Server-side only
- Server-side: Upload offline conversions
- CRM integration: Phone orders, in-store sales
Decision Matrix
| Scenario | Client-Side | Server-Side | Hybrid |
|---|---|---|---|
| Basic website tracking | ✓ | ||
| High ad blocker audience | ✓ | ✓ | |
| Privacy-first approach | ✓ | ||
| Offline conversions | ✓ | ||
| Real-time attribution | ✓ | ✓ | |
| Critical conversions | ✓ | ||
| Email available | ✓ | ✓ | |
| Dynamic retargeting | ✓ | ✓ |
Performance Comparison
| Metric | Client-Side | Server-Side | Hybrid |
|---|---|---|---|
| Implementation Time | 1-2 days | 3-5 days | 4-7 days |
| Tracking Accuracy (with blockers) | 60-80% | 95-99% | 95-99% |
| Real-time Attribution | Yes | Slight delay | Yes |
| Privacy Compliance | Moderate | High | High |
| Maintenance | Low | Medium | Medium |
| Cost | Low | Medium | Medium |
Best Practices
- Start with client-side, add server-side for critical conversions
- Always hash emails server-side, never send plaintext
- Use order IDs to prevent duplicate conversion counting
- Implement retry logic for failed server-side API calls
- Monitor both methods to identify tracking gaps
- Test in staging before production deployment
- Document setup for team reference
- Set up alerts for API failures
- Respect user privacy - only collect necessary data
- Regular audits of tracking accuracy
Troubleshooting
Client-Side Issues
- Pixel not loading: Check browser console for errors
- Events not firing: Verify Pixel Helper shows events
- Ad blockers: Test in incognito mode
Server-Side Issues
- API authentication fails: Verify access token is valid
- Events not appearing: Check event_time is in correct format (Unix timestamp)
- User matching fails: Ensure email is properly hashed (SHA-256, lowercase, trimmed)
Hybrid Issues
- Duplicate conversions: Verify order ID deduplication logic
- Inconsistent data: Validate data format across both methods