Contextual Signals
How to classify IP type, detect timezone mismatches, and categorize referrer sources for age assurance.
Overview
Contextual signals carry a 10% weight in the Signal Fusion engine. Unlike behavioral and input signals, some contextual signals require server-side computation — particularly IP classification, which needs an IP intelligence database.
Two of the three contextual signals (ip_type and referrer_category) may
use external databases (IP reputation lookups, domain classification).
When these fields are present, the API response sets
internal_evidence_only: false. If your compliance posture requires strictly
internal evidence under §1798.501(b)(2)(B), omit these fields and use only
timezone_offset_delta_minutes.
Fields Reference
| Parameter | Type | Required | Description |
|---|---|---|---|
ip_type | enum | Optional | IP network classification: residential, education, datacenter, or corporate. |
timezone_offset_delta_minutes | number | Optional | Mismatch in minutes between the user's system timezone and the expected timezone from IP geolocation. |
referrer_category | enum | Optional | Classification of the referring traffic source: direct, social_minor, social_general, parental_control, search_engine, or unknown. |
All three fields are optional — send whichever ones you can determine. Even a single contextual signal contributes to the assessment.
IP Type
What it measures: The classification of the user's IP address. Education network IPs are strong minor indicators (schools have concentrations of children). Datacenter IPs suggest VPN usage, which can indicate age circumvention. Corporate IPs are mild adult indicators.
Signal impact:
| IP Type | Score Effect | Why |
|---|---|---|
education | -0.30 (strong minor) | Schools, universities, library networks |
datacenter | -0.15 (suspicious) | VPN/proxy — possible age circumvention |
residential | 0.00 (neutral) | Default home connection |
corporate | +0.10 (mild adult) | Enterprise office networks |
How to Collect
IP classification is a server-side concern. Your backend determines the user's IP address from the incoming request and looks it up against an IP intelligence provider.
// IP classification is server-side — your React app calls
// your backend, which classifies the IP before forwarding to A3.
async function fetchIpType(): Promise<string> {
const res = await fetch('/api/classify-ip');
const { ip_type } = await res.json();
return ip_type; // "residential", "education", "datacenter", "corporate"
}
// Usage in your component:
// const ipType = await fetchIpType();Recommended IP Providers
| Provider | Database | Free Tier |
|---|---|---|
| MaxMind GeoLite2 | ASN database (local) | Yes (with registration) |
| IPinfo | REST API | 50K lookups/month |
| ip-api.com | REST API | 45 req/min (non-commercial) |
For lowest latency, use a local database (MaxMind GeoLite2 ASN) rather than a REST API. The ASN database is updated weekly and supports offline classification with sub-millisecond lookups.
Timezone Offset Delta
What it measures: The mismatch between the user's system clock timezone and the timezone expected from their IP-based geolocation. A mismatch greater than 60 minutes can indicate VPN usage or timezone manipulation — a classic bypass technique.
Signal impact: Mismatches > 60 minutes reduce the contextual score by 0.20
and tag the response with inconsistent_timezone_offset.
How to Collect
The client reports its system timezone. Your server compares it against the expected timezone from IP geolocation.
// Client-side: collect the timezone offset and send to your backend
function getTimezoneOffsetMinutes(): number {
return new Date().getTimezoneOffset();
}
// Send to your backend which compares against IP geolocation:
// fetch('/api/timezone-delta', {
// method: 'POST',
// body: JSON.stringify({ tz_offset: getTimezoneOffsetMinutes() }),
// });Edge Cases
- Legitimate travelers may have a timezone mismatch (e.g., using a US phone in Europe). The -0.20 penalty is intentionally moderate — it flags the mismatch but doesn't dominate the score.
- Users who manually set their timezone will show a mismatch even without a VPN. This is uncommon but possible.
Referrer Category
What it measures: The classification of the traffic source that brought the user to your app. Referrals from parental control platforms or minor-oriented social networks are strong minor indicators.
Signal impact:
| Category | Score Effect | Examples |
|---|---|---|
parental_control | -0.25 (strong minor) | Bark, Qustodio, Net Nanny, Family Link |
social_minor | -0.20 (minor indicator) | YouTube Kids, Messenger Kids, PopJam |
social_general | 0.00 (neutral) | Instagram, TikTok, Twitter/X, Facebook |
search_engine | 0.00 (neutral) | Google, Bing, DuckDuckGo |
direct | 0.00 (neutral) | No referrer (direct navigation or bookmarks) |
unknown | 0.00 (neutral) | Unrecognized referrer |
How to Collect
Read document.referrer on the client and classify the domain against known
categories.
import { useMemo } from 'react';
type ReferrerCategory =
| 'direct' | 'social_minor' | 'social_general'
| 'parental_control' | 'search_engine' | 'unknown';
const PATTERNS: [ReferrerCategory, RegExp][] = [
['parental_control', /bark\.us|qustodio|netnanny|familylink|kaspersky.*safe|norton.*family|screentime/i],
['social_minor', /youtubekids|messengerkids|popjam|grom\.social|kinzoo/i],
['social_general', /instagram|tiktok|twitter|x\.com|facebook|snapchat|reddit|linkedin|threads\.net/i],
['search_engine', /google\.|bing\.|duckduckgo|yahoo\.|baidu|yandex/i],
];
function classifyReferrer(referrer: string): ReferrerCategory {
if (!referrer) return 'direct';
try {
const hostname = new URL(referrer).hostname;
for (const [category, pattern] of PATTERNS) {
if (pattern.test(hostname)) return category;
}
} catch {}
return 'unknown';
}
export function useReferrerCategory(): ReferrerCategory {
return useMemo(() => classifyReferrer(document.referrer), []);
}
// Usage: const referrer_category = useReferrerCategory();You can classify the referrer either client-side (using
document.referrer) or server-side (using the Referer HTTP header).
Server-side is slightly more reliable since document.referrer can be
empty in some privacy-focused browsers.
Putting It All Together
# Complete contextual_signals example payload:
curl -X POST https://api.a3api.io/v1/assurance/assess-age \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"os_signal": "18-plus",
"user_country_code": "US",
"contextual_signals": {
"ip_type": "residential",
"timezone_offset_delta_minutes": 0,
"referrer_category": "search_engine"
}
}'Next Steps
- Account Longevity — the simplest signal to implement
- Behavioral Metrics — the highest-impact category (43%)
- Signal Collection Overview — integration paths and coverage guidance