Zenovay treats IP addresses as personal data. The raw address is used at request time for geolocation, rate limiting, and abuse detection, and it is hashed before it reaches your analytics tables. This article explains exactly what happens to an IP, including the specific places a raw address does exist and for how long.
How IPs Are Handled
When a tracking request arrives, the visitor's raw IP does a small amount of operational work and is then replaced by a hash for everything you see in your dashboard. Nothing in this flow is configurable: it is the default behaviour for every website on every plan.
- Geolocation: Determine country, region, and (approximate) city.
- Rate limiting: Count requests per address so a single source cannot flood ingestion.
- Abuse and bot detection: Filter automated and malicious traffic, and block sources that keep abusing the endpoint.
- Hashing: Derive a daily-rotating SHA-256 identifier. From this point the analytics pipeline works with the hash, not the address.
The Process
Incoming Request
↓
IP Address (e.g., 203.0.113.50)
↓
┌─────────────────────────────┐
│ Geolocation Lookup │
│ → Country: United States │
│ → Region: California │
│ → City: San Francisco │
│ (result cached for 24h) │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ Rate limit + abuse checks │
│ → allow / throttle / block │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ Visitor ID Generation │
│ Daily-rotating SHA-256 │
│ → v_abc123def456 │
└─────────────────────────────┘
↓
Analytics tables receive the hash
(plus geo and device fields, no raw address)
What the Analytics Tables Store
Your analytics data holds a hash, not an address. The ip_address column on the visitor record is written empty, and only the hash and the resolved geolocation are kept:
- The visitor identifier is derived from the address, the website ID, and the calendar date.
- Because the date is part of the input, the identifier rotates daily. A returning visitor produces a different identifier tomorrow than today, so the visitor identifier does not build a long-term profile of one address.
- Audit logs hold no plaintext address either. The plaintext column was removed from the account audit log, and the team audit log hashes the value on insert.
This is the behaviour on every plan, and there is no setting that turns it off.
Treat the hash as pseudonymous data rather than anonymous data. It is a one-way function with no key that turns it back into an address, but the inputs are the address, the website ID, and the date, so someone holding a hash and a candidate address can test that candidate. Your own privacy notice should describe it that way.
Where a Raw Address Does Exist
Being precise about this is more useful than a blanket claim. Outside the analytics tables, the raw address appears in these places, each of them bounded:
| Where | Why | How long |
|---|---|---|
| Geolocation providers | Country, region, and city lookups are sent to ipinfo.io and ipwho.is with the address in the request | Governed by each provider's own retention |
| Geolocation cache | Avoids repeating the same lookup for the same address | 24 hours |
| Rate-limit counters | Per-address request counting at the edge | Minutes, the length of the rate-limit window |
| Blocklist entries | Temporarily blocking a source that is abusing ingestion | 24 hours |
| Ban records | Longer blocks for repeat abuse | 30 days by default |
| Security events and reputation records | Abuse detection needs history to distinguish a spike from an attack | 7 days for events, 30 days for reputation |
| Abuse-prevention and device-authorization tables | Records of abuse signals, device authorization codes, and bans | Until the record is purged by its own retention |
| Infrastructure logs | Rate-limit, security, and block events are logged by Cloudflare Workers | Governed by the platform log retention |
None of this feeds your dashboard, and none of it is exposed through the API or exports. It exists so that ingestion can resolve a location and stay available under attack.
Geolocation Accuracy
Geolocation is derived from the IP at request time. Accuracy decreases as you move from country to city level:
Full IP (203.0.113.50):
└── Country: United States
└── Region: California
└── City: San Francisco (approximate)
City-level results are best-effort and can be imprecise, especially for mobile carriers, corporate networks, and VPN traffic. Country-level data is the most reliable.
Server-Side Tracking
Forwarding the Visitor IP
When you send events from your own backend rather than the browser, Zenovay sees your server's IP unless you forward the original visitor IP. Pass it along with the standard X-Forwarded-For header so geolocation reflects the real visitor:
// Express.js example
app.post('/track', async (req, res) => {
const clientIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': clientIP // Forward for geolocation
},
body: JSON.stringify({
type: 'page_view',
url: 'https://yoursite.com/page'
})
});
res.sendStatus(204);
});
Whatever IP you forward is treated exactly like a browser request: geolocated, checked against the rate limit and abuse rules, and hashed before it reaches your analytics tables. Forwarding an address means you are sending it to the same geolocation providers listed above, so forward only what you need geolocation for.
Compliance Considerations
GDPR
IP addresses are personal data under GDPR. Zenovay keeps the address out of your analytics tables, replaces it with a daily-rotating hash, and confines the raw value to geolocation and abuse prevention with the retention limits listed above. Document your analytics processing in your own privacy policy, and note that the geolocation lookups are a transfer to those providers.
CCPA
IP addresses count as personal information under CCPA. Disclose your use of analytics in your privacy policy. What you retain and can act on in Zenovay is a hash plus coarse location, not the address itself. Whether that changes any of your own disclosure or opt-out duties depends on your jurisdiction.
Technical Details
Visitor ID Hash
The visitor identifier is built like this:
Visitor_ID = SHA-256( IP_Address + "|" + Website_ID + "|" + Date )
Where Date is the current calendar date (e.g. "2026-08-17").
Example:
SHA-256("203.0.113.50|abc123|2026-08-17") = "a3b8c9d4e5f6..."
Properties:
- One-way: SHA-256 has no inverse, and there is no key or lookup table that turns a stored identifier back into an address. That is not the same as unguessable, which is why the note above matters.
- Scoped per website: the same visitor on two of your sites produces two different visitor identifiers, so visits are not correlated across your properties by this identifier.
- Consistent within a day: the same visitor on the same day and site produces the same identifier.
- Rotates daily: the date is part of the input, so the identifier changes every day and is not a permanent identifier.
One identifier deliberately behaves differently. B2B company identification uses a separate hash that neither rotates daily nor is scoped per website, because correlating visits from the same company network over time is the entire point of the feature. Daily rotation and per-website scoping apply to the visitor and session identifiers, not to that one.
Geolocation Source
Zenovay resolves country and approximate location at request time from Cloudflare's edge geolocation and from lookups against two external providers, ipinfo.io and ipwho.is. Results are cached for 24 hours so a repeat visitor does not trigger a repeat lookup. Accuracy varies by region and is most reliable at the country level.
Troubleshooting
No Geographic Data
If geolocation isn't working:
- Make sure the visitor IP is being forwarded when tracking server-side (behind a proxy or load balancer).
- Confirm you aren't testing from a localhost or internal IP, which is skipped.
Inaccurate Location
Geolocation can be wrong due to:
- VPN usage
- Mobile carriers
- Corporate proxies
- Geo-IP database lag