This guide shows how to integrate Third Audience citation tracking with headless WordPress sites (Next.js, Gatsby, Nuxt, etc.).
Key Feature: AJAX-first architecture works with ALL security plugins - no REST API issues!
Most production WordPress sites use security plugins (Solid Security, Wordfence, etc.) that block REST API.
| Method | Security Plugin Friendly? | Configuration Needed? |
|---|---|---|
| AJAX | ✅ 100% | ❌ None |
| REST API | ❌ Blocked 80%+ | ✅ Manual whitelist |
| GraphQL | ✅ Requires WPGraphQL |
AJAX (admin-ajax.php) has been the WordPress standard since 2.8 (2008). Security plugins NEVER block it.
# In your WordPress installation
cd wp-content/plugins/
git clone https://github.com/your-repo/third-audience.git
# Or upload via WordPress Admin → Plugins → Add NewActivate the plugin - it will auto-configure everything!
Go to: WordPress Admin → Settings → Third Audience
Copy your API key:
ta_xxxxxxxxxxxxxxxxxxxxxxxxxx
In your headless site (Next.js example):
# .env.local or .env.production
WORDPRESS_URL=https://your-wordpress-site.com
TA_CITATION_API_KEY=ta_xxxxxxxxxxxxxxxxxxxxxxxxxxCreate or update src/middleware.ts:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// AI platforms that cite content
const AI_CITATION_SOURCES = [
{ pattern: /chatgpt/i, name: 'ChatGPT' },
{ pattern: /perplexity/i, name: 'Perplexity' },
{ pattern: /claude/i, name: 'Claude' },
{ pattern: /gemini/i, name: 'Gemini' },
{ pattern: /copilot/i, name: 'Copilot' },
{ pattern: /bing/i, name: 'Bing AI' },
];
/**
* Detect if request came from an AI citation
*/
function detectAICitation(request: NextRequest): { platform: string; query?: string } | null {
const url = request.nextUrl;
const referer = request.headers.get('referer') || '';
// Check utm_source parameter (e.g., ?utm_source=chatgpt.com)
const utmSource = url.searchParams.get('utm_source');
if (utmSource) {
for (const source of AI_CITATION_SOURCES) {
if (source.pattern.test(utmSource)) {
return { platform: source.name };
}
}
}
// Check referer header
for (const source of AI_CITATION_SOURCES) {
if (source.pattern.test(referer)) {
// Extract search query from Perplexity
if (source.name === 'Perplexity' && referer.includes('?q=')) {
const match = referer.match(/[?&]q=([^&]+)/);
return {
platform: source.name,
query: match ? decodeURIComponent(match[1]) : undefined
};
}
return { platform: source.name };
}
}
return null;
}
/**
* Track citation using AJAX-FIRST architecture
*
* Why AJAX first?
* - ✅ Works with ALL security plugins (Solid Security, Wordfence, etc.)
* - ✅ Standard WordPress API method since WP 2.8
* - ✅ No REST API conflicts or blocks
* - ✅ Same speed and features as REST API
*
* Method priority:
* 1. AJAX (primary - always works)
* 2. REST API (fallback - may be blocked)
* 3. GraphQL (last resort - requires WPGraphQL plugin)
*/
async function trackCitation(request: NextRequest, citation: { platform: string; query?: string }) {
const wordpressUrl = process.env.WORDPRESS_URL || 'https://your-site.com';
const apiKey = process.env.TA_CITATION_API_KEY || '';
const data = {
url: request.nextUrl.pathname,
platform: citation.platform,
referer: request.headers.get('referer') || '',
search_query: citation.query || '',
ip: request.headers.get('x-forwarded-for')?.split(',')[0] || 'unknown',
};
try {
// METHOD 1: Try AJAX first (most reliable - works with ALL security plugins)
const ajaxResponse = await fetch(`${wordpressUrl}/wp-admin/admin-ajax.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'ta_track_citation',
api_key: apiKey,
...data,
}),
});
if (ajaxResponse.ok) {
console.log('[Citation Tracking] ✅ Tracked via AJAX (Primary method)');
return;
}
console.log('[Citation Tracking] AJAX failed (unusual), trying REST API...');
// METHOD 2: Try REST API (backup - may be blocked by security plugins)
const restResponse = await fetch(`${wordpressUrl}/wp-json/third-audience/v1/track-citation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-TA-Api-Key': apiKey,
},
body: JSON.stringify(data),
});
if (restResponse.ok) {
console.log('[Citation Tracking] ✅ Tracked via REST API (Fallback)');
return;
}
console.log('[Citation Tracking] REST API also failed, trying GraphQL...');
// METHOD 3: Try GraphQL (if WPGraphQL is available)
const graphqlResponse = await fetch(`${wordpressUrl}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation TrackCitation($url: String!, $platform: String!, $referer: String, $searchQuery: String, $apiKey: String!) {
trackCitation(input: {
url: $url
platform: $platform
referer: $referer
searchQuery: $searchQuery
apiKey: $apiKey
}) {
success
message
}
}
`,
variables: {
url: data.url,
platform: data.platform,
referer: data.referer,
searchQuery: data.search_query,
apiKey: apiKey,
},
}),
});
if (graphqlResponse.ok) {
const result = await graphqlResponse.json();
if (result.data?.trackCitation?.success) {
console.log('[Citation Tracking] ✅ Tracked via GraphQL (Last resort fallback)');
return;
}
}
console.error('[Citation Tracking] ❌ All 3 methods failed (AJAX, REST, GraphQL) - check WordPress plugin');
} catch (error) {
console.error('[Citation Tracking] ❌ Network error:', error);
}
}
export async function middleware(request: NextRequest) {
// 1. AI Citation Tracking (fire and forget, non-blocking)
const citation = detectAICitation(request);
if (citation) {
// Track asynchronously (non-blocking)
trackCitation(request, citation);
}
// 2. Continue with your existing middleware logic
return NextResponse.next();
}
export const config = {
matcher: [
// Match all paths except static files and API routes
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};If you prefer API routes, create app/api/track-citation/route.ts:
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
const wordpressUrl = process.env.WORDPRESS_URL || '';
const apiKey = process.env.TA_CITATION_API_KEY || '';
try {
// AJAX-first approach
const response = await fetch(`${wordpressUrl}/wp-admin/admin-ajax.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'ta_track_citation',
api_key: apiKey,
...body,
}),
});
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error: 'Failed to track citation' }, { status: 500 });
}
}exports.onCreatePage = async ({ page, actions }) => {
const { createPage, deletePage } = actions;
// Track citation on page visit
if (typeof window !== 'undefined') {
const referer = document.referrer;
const aiPlatforms = ['perplexity', 'chatgpt', 'claude', 'gemini'];
const isAICitation = aiPlatforms.some(platform =>
referer.toLowerCase().includes(platform)
);
if (isAICitation) {
fetch(`${process.env.WORDPRESS_URL}/wp-admin/admin-ajax.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ta_track_citation',
api_key: process.env.TA_CITATION_API_KEY,
platform: referer.match(/perplexity|chatgpt|claude|gemini/i)[0],
url: window.location.pathname,
referer: referer,
ip: 'client-side',
})
});
}
}
};export default defineEventHandler(async (event) => {
const referer = getHeader(event, 'referer') || '';
const aiPlatforms = ['perplexity', 'chatgpt', 'claude', 'gemini'];
const matchedPlatform = aiPlatforms.find(platform =>
referer.toLowerCase().includes(platform)
);
if (matchedPlatform) {
const config = useRuntimeConfig();
try {
await $fetch(`${config.public.wordpressUrl}/wp-admin/admin-ajax.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'ta_track_citation',
api_key: config.taCitationApiKey,
platform: matchedPlatform,
url: event.path,
referer: referer,
ip: getHeader(event, 'x-forwarded-for') || 'unknown',
})
});
} catch (error) {
console.error('Citation tracking failed:', error);
}
}
});# Test AJAX health check
curl -X POST "https://your-wordpress-site.com/wp-admin/admin-ajax.php" \
-d "action=ta_health_check"
# Expected: {"success":true,"data":{"status":"healthy","version":"3.4.3","method":"ajax_fallback"}}
# If returns "0" = AJAX action not registered (plugin not activated)# Test AJAX citation tracking
curl -X POST "https://your-wordpress-site.com/wp-admin/admin-ajax.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=ta_track_citation" \
-d "api_key=YOUR_API_KEY" \
-d "platform=Perplexity" \
-d "url=/test-page/" \
-d "referer=https://www.perplexity.ai" \
-d "search_query=test" \
-d "ip=8.8.8.8"
# Expected: {"success":true,"data":{"message":"Citation tracked successfully","platform":"Perplexity"}}Visit your site with AI platform referer:
https://your-site.com/blog/some-post?utm_source=perplexity
Check browser console:
[Citation Tracking] ✅ Tracked via AJAX (Primary method)
Go to: WordPress Admin → Third Audience → AI Citations
You should see the tracked citation!
Cause: AJAX action not registered Fix:
- Check plugin is activated in WordPress Admin → Plugins
- Deactivate and reactivate the plugin
- Check WordPress debug.log for errors
Cause: API key mismatch Fix:
- Get API key from WordPress Admin → Settings → Third Audience
- Update your
.envfile with correct key - Restart your dev server
Cause: Database issue or old plugin version Fix:
- Go to WordPress Admin → Settings → Third Audience → System Health
- Check Database Status
- Click "Run Database Migration" if available
- Ensure plugin version is 3.4.0+
Cause: Network/firewall issue Fix:
- Check WordPress URL is correct in
.env - Test direct access:
curl https://your-wp-site.com/wp-admin/admin-ajax.php - Check firewall rules (Cloudflare, server firewall)
Endpoint: POST /wp-admin/admin-ajax.php
Parameters:
action=ta_track_citation (required)
api_key=YOUR_API_KEY (required)
platform=ChatGPT|Perplexity|Claude|Gemini (required)
url=/page-path/ (required)
referer=https://perplexity.ai (optional)
search_query=user search query (optional)
ip=8.8.8.8 (optional)
Response (Success):
{
"success": true,
"data": {
"message": "Citation tracked successfully",
"platform": "Perplexity",
"url": "/page-path/",
"method": "ajax_fallback"
}
}Response (Error):
{
"success": false,
"data": {
"message": "Invalid or missing API key"
}
}Endpoint: POST /wp-json/third-audience/v1/track-citation
Headers:
Content-Type: application/json
X-TA-Api-Key: YOUR_API_KEY
Body:
{
"url": "/page-path/",
"platform": "Perplexity",
"referer": "https://perplexity.ai",
"search_query": "optional query",
"ip": "8.8.8.8"
}Note: REST API may be blocked by security plugins. AJAX is recommended.
# .env.local (never commit to git)
TA_CITATION_API_KEY=ta_xxxxxxxxxxxxx
# Add to .gitignore
echo ".env.local" >> .gitignoreThe plugin includes built-in rate limiting:
- 30 requests per minute per IP (AJAX)
- 60 requests per minute per IP (REST API)
To rotate your API key:
- Go to WordPress Admin → Settings → Third Audience
- Click "Regenerate API Key"
- Update your
.envfile - Redeploy your frontend
The middleware code runs tracking asynchronously - it never blocks your page load:
// Track asynchronously (fire and forget)
trackCitation(request, citation);
// Page continues loading immediately
return NextResponse.next();Citations are deduplicated within 60-second window to prevent double-tracking.
- AJAX request: ~50-100ms
- No database queries on frontend
- Runs only when AI referer detected
Add custom AI platforms:
const AI_CITATION_SOURCES = [
{ pattern: /chatgpt/i, name: 'ChatGPT' },
{ pattern: /perplexity/i, name: 'Perplexity' },
{ pattern: /your-custom-ai/i, name: 'CustomAI' }, // Add yours
];Override IP detection logic:
const data = {
// ...other fields
ip: request.headers.get('cf-connecting-ip') || // Cloudflare
request.headers.get('x-real-ip') || // Nginx
request.headers.get('x-forwarded-for')?.split(',')[0] ||
'unknown',
};Enable detailed logging:
console.log('[Citation Tracking] Detection:', citation);
console.log('[Citation Tracking] Request data:', data);
console.log('[Citation Tracking] Response:', response.status);Go to: WordPress Admin → Settings → Third Audience → System Health
You'll see:
✅ Running in AJAX Mode (Secure & Reliable)
Third Audience is using AJAX endpoints - the standard WordPress API
method that works with ALL security plugins.
Why this is better for production sites:
✅ Compatible with security plugins: Solid Security
✅ Works on headless WordPress sites
✅ No security plugin conflicts
✅ Same features as REST API, zero compromises
No action required - your site is configured optimally!
If you want to use REST API despite security plugins:
- Go to System Health page
- Click "Force REST API Mode"
- This will attempt to whitelist REST endpoints in security plugins
Note: Not recommended - AJAX works better universally.
Issue: "0" response from AJAX Solution: Plugin not activated or AJAX actions not registered
Issue: "Invalid API key" Solution: Check API key matches WordPress settings
Issue: Citations not showing in dashboard Solution: Run database migration from System Health page
Enable WordPress debug logging:
// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);Check logs:
tail -f wp-content/debug.log- Check System Health page first
- Review debug.log for errors
- Test AJAX endpoint manually (see Testing section)
- Report issues with full environment details
- Universal compatibility (ALL security plugins)
- Zero configuration needed
- Automatic fallback to REST/GraphQL
- Non-blocking, async tracking
- Built-in rate limiting
- Duplicate prevention
- IP geolocation
- Full analytics dashboard
- Install plugin on WordPress
- Activate plugin (auto-configures everything)
- Get API key from Settings
- Add to your
.envfile - Copy middleware code to your project
- Test with
?utm_source=perplexity - Check WordPress dashboard for citation
That's it! Your headless site now tracks AI citations automatically. 🎉