Custom Endpoint
Custom Endpoint
CookieDialog allows you to specify a custom geolocation endpoint for IP-based location detection instead of the default ipapi.co
service.
Basic Custom Endpoint
CookieDialog.init({ enableLocation: true, locationEndpoint: 'https://api.mysite.com/geolocation'});
Required Response Format
Your custom endpoint must return JSON with one of these country code fields:
Option 1: country_code
(recommended)
{ "country_code": "DE"}
Option 2: country
{ "country": "FR"}
Option 3: countryCode
{ "countryCode": "ES"}
Implementation Examples
Node.js/Express Endpoint
const express = require('express');const geoip = require('geoip-lite');const app = express();
app.get('/geolocation', (req, res) => { // Get client IP const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || (req.connection.socket ? req.connection.socket.remoteAddress : null);
// Look up location const geo = geoip.lookup(clientIP);
if (geo && geo.country) { res.json({ country_code: geo.country }); } else { // Fallback - assume GDPR region res.json({ country_code: 'UNKNOWN' }); }});
app.listen(3000);
PHP Endpoint
<?phpheader('Content-Type: application/json');header('Access-Control-Allow-Origin: *');
function getClientIP() { $ipkeys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR']; foreach ($ipkeys as $key) { if (array_key_exists($key, $_SERVER) && !empty($_SERVER[$key])) { $ip = $_SERVER[$key]; if (strpos($ip, ',') !== false) { $ip = explode(',', $ip)[0]; } if (filter_var(trim($ip), FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return trim($ip); } } } return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';}
function getCountryFromIP($ip) { // Use ipapi.co as fallback $response = file_get_contents("https://ipapi.co/{$ip}/json/"); $data = json_decode($response, true);
return $data['country_code'] ?? 'UNKNOWN';}
$clientIP = getClientIP();$country = getCountryFromIP($clientIP);
echo json_encode(['country_code' => $country]);?>
Python/Flask Endpoint
from flask import Flask, request, jsonifyimport geoip2.databaseimport geoip2.errors
app = Flask(__name__)
# Download GeoLite2 database from MaxMindreader = geoip2.database.Reader('GeoLite2-Country.mmdb')
@app.route('/geolocation')def geolocation(): # Get client IP if request.headers.getlist("X-Forwarded-For"): client_ip = request.headers.getlist("X-Forwarded-For")[0] else: client_ip = request.remote_addr
try: response = reader.country(client_ip) country_code = response.country.iso_code return jsonify({'country_code': country_code}) except geoip2.errors.AddressNotFoundError: return jsonify({'country_code': 'UNKNOWN'})
if __name__ == '__main__': app.run()
Cloudflare Workers Endpoint
export default { async fetch(request, env, ctx) { // Cloudflare provides country in headers const country = request.headers.get('CF-IPCountry');
const response = { country_code: country || 'UNKNOWN' };
return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'public, max-age=3600' } }); }};
Advanced Response Format
Your endpoint can return additional data:
{ "country_code": "DE", "country_name": "Germany", "is_gdpr_region": true, "continent": "EU", "city": "Berlin", "timezone": "Europe/Berlin"}
CookieDialog will only use the country code, but you can access the full response in callbacks:
CookieDialog.init({ enableLocation: true, locationEndpoint: 'https://api.mysite.com/geolocation', onLocationSuccess: (locationData) => { console.log('Full location data:', locationData); if (locationData.city) { console.log('User is in:', locationData.city); } }});
Error Handling
Your endpoint should handle errors gracefully:
// Node.js example with error handlingapp.get('/geolocation', async (req, res) => { try { const clientIP = getClientIP(req);
if (!isValidIP(clientIP)) { return res.json({ country_code: 'UNKNOWN', error: 'Invalid IP address' }); }
const geo = await lookupLocation(clientIP);
res.json({ country_code: geo.country || 'UNKNOWN', success: true }); } catch (error) { console.error('Geolocation error:', error);
// Return UNKNOWN to trigger dialog display (fail-safe) res.json({ country_code: 'UNKNOWN', error: 'Location lookup failed', success: false }); }});
Caching Strategies
Server-side Caching
const NodeCache = require('node-cache');const cache = new NodeCache({ stdTTL: 3600 }); // 1 hour
app.get('/geolocation', (req, res) => { const clientIP = getClientIP(req); const cacheKey = `geo_${clientIP}`;
// Check cache first const cached = cache.get(cacheKey); if (cached) { return res.json(cached); }
// Lookup and cache result const geo = geoip.lookup(clientIP); const result = { country_code: geo?.country || 'UNKNOWN' };
cache.set(cacheKey, result); res.json(result);});
CDN/Edge Caching
// Add cache headersapp.get('/geolocation', (req, res) => { const geo = performLookup(req);
res.set({ 'Cache-Control': 'public, max-age=3600', 'Vary': 'X-Forwarded-For' });
res.json({ country_code: geo.country });});
Security Considerations
Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs});
app.use('/geolocation', limiter);
Input Validation
function isValidIP(ip) { const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv4Regex.test(ip) || ipv6Regex.test(ip);}
app.get('/geolocation', (req, res) => { const clientIP = getClientIP(req);
if (!isValidIP(clientIP)) { return res.status(400).json({ error: 'Invalid IP address', country_code: 'UNKNOWN' }); }
// Continue with lookup...});
Testing Your Endpoint
Test with Different IPs
# Test with German IPcurl "https://api.yoursite.com/geolocation" \ -H "X-Forwarded-For: 93.184.216.34"
# Test with US IPcurl "https://api.yoursite.com/geolocation" \ -H "X-Forwarded-For: 8.8.8.8"
Automated Testing
// Test scriptconst axios = require('axios');
const testIPs = { 'German IP': '93.184.216.34', 'US IP': '8.8.8.8', 'UK IP': '81.2.69.142'};
async function testEndpoint() { for (const [name, ip] of Object.entries(testIPs)) { try { const response = await axios.get('https://api.yoursite.com/geolocation', { headers: { 'X-Forwarded-For': ip } });
console.log(`${name}: ${response.data.country_code}`); } catch (error) { console.error(`${name}: Error - ${error.message}`); } }}
testEndpoint();
Fallback Chains
Implement multiple fallback services:
const endpoints = [ 'https://api.mysite.com/geolocation', 'https://backup.mysite.com/geo', 'https://ipapi.co/json/'];
async function getLocationWithFallback() { for (const endpoint of endpoints) { try { const response = await fetch(endpoint); const data = await response.json();
if (data.country_code || data.country) { return data; } } catch (error) { console.warn(`Endpoint ${endpoint} failed:`, error); } }
// All endpoints failed return { country_code: 'UNKNOWN' };}
// Use with CookieDialogCookieDialog.init({ enableLocation: true, customLocationFunction: getLocationWithFallback});
Performance Optimization
Lazy Loading
Only load geolocation when needed:
let locationPromise = null;
function getLocation() { if (!locationPromise) { locationPromise = fetch('https://api.mysite.com/geolocation') .then(response => response.json()); } return locationPromise;}
Preloading
Preload location data:
<link rel="preload" href="https://api.mysite.com/geolocation" as="fetch" crossorigin>
Compliance Considerations
GDPR Compliance
Your endpoint should:
- Not store IP addresses longer than necessary
- Include appropriate privacy notices
- Handle data subject requests
Example Privacy-First Implementation
app.get('/geolocation', (req, res) => { const clientIP = getClientIP(req);
// Perform lookup without storing IP const geo = geoip.lookup(clientIP);
// Log anonymized request (optional) console.log(`Location request from ${anonymizeIP(clientIP)}: ${geo?.country}`);
res.json({ country_code: geo?.country || 'UNKNOWN', privacy_notice: 'IP address used only for location determination and not stored' });});
function anonymizeIP(ip) { // Anonymize last octet for IPv4 return ip.replace(/\.\d+$/, '.xxx');}