The geo block
Any activity may say where someone is, in a list of blocks. The list is a top-level field of the activity, beside identifiers, not inside features: each block points at one of the activity's identifiers by its role. A whole request, with the list in place and the rest shortened to placeholders (…); here a newsletter sign-up from a backend that asked for the address:
{
"batch": [
{
"activity_id": "0192f1a4-7c3e-7b21-9d0a-5e3f8c2a1b45",
"activity": "subscribed_newsletter",
"ts": "2026-10-17T20:00:00+02:00",
"identifiers": [{ "type": "email", "value": "ana@example.com", "role": "subscriber" }],
"features": { "list": "…" },
"geo": [
{ "kind": "home", "role": "subscriber", "city": "Amsterdam", "postal_code": "1012 AB", "country": "NL", "locale": "nl_NL" },
{ "kind": "ip", "role": "subscriber", "ip": "192.0.2.10" }
]
}
]
}
Every kind of block, as the list may hold them:
"geo": [
{ "kind": "home", "role": "buyer", "city": "Amsterdma", "postal_code": "1012 AB", "country": "Nederland", "locale": "nl_NL" },
{ "kind": "ip", "role": "buyer", "ip": "192.0.2.10" },
{ "kind": "device", "role": "holder", "lat": 52.3731, "lon": 4.8926 },
{ "kind": "action", "role": "holder", "lat": 52.37312, "lon": 4.89261 }
]
| Field | Meaning |
|---|---|
kind | What the block locates: home (an address as typed), ip (where the person connected from), device (a position fix from an app), action (where the activity happened: a gate, a bar). Required. |
role | The identifier the block is about (buyer, holder); without one, the first identifier that is not the anonymous device. |
city, postal_code | For home: as typed. |
country | A name in any language or an ISO 3166-1 code. On any kind. |
locale | The sender's locale for the person (nl_NL, de-DE): not an answer, only a hint for the country. |
ip | For ip: an IPv4 or IPv6 address. |
lat, lon | For device and action: decimal degrees. |
A block is accepted from server and platform keys. One sent with a browser key is dropped and counted: a public key must not place a person. A block that says nothing, has an unknown kind, an IP the database does not know, or names a role no identifier has is dropped and counted; the activity is kept.
What is stored, and what is not
The block itself is never stored: the processor consumes it and features never hold it. What it writes, one row per block beside the stream:
| Kind | Stored | Dropped |
|---|---|---|
home | normalised lookup keys for the city and the postal district, and a country hint | nothing; the text as typed is also an attribute observation |
ip | the city the IP database puts the address in, with that city's coordinates | the IP address, after one lookup |
device | the cell the fix fell in: 0.01° of latitude and longitude, about a kilometre, the order of a postal district | the fix itself |
action | the exact position, and its cell for the town and the country | nothing |
A place that says where a person lives or was is never more precise than a postal district or a town centroid: no street, no house, no exact position. An action is the exception: it says where the activity happened, at the organiser's own gate or bar, and that point is kept exactly so that scans can be drawn on a map.
Scans on a map
Send an action block on the scan (or any activity) with the position of the gate, with the ticket's barcode as the role. The point is kept exactly, with the town from its cell, so the scans of one entry can be counted and drawn on a map, and joined to the people who used it.
The Weeztix scan webhook carries no position yet; when it does, the mapper adds the block and the map fills by itself.
How a place is resolved
The keys are resolved in the background, once per distinct key, into a dictionary from keys to places, against GeoNames and the DB-IP city database loaded in the service. A typo is tolerated (Amsterdma), a name in another language is known (Den Bosch, Muenchen), a longer text is searched for a place name (Den Haag Centrum), and a cell resolves to the nearest postal district within 3 km or the nearest town within 25 km. Text that matches several places equally well stays unresolved: a wrong place is worse than none. A wrong or missing resolution is fixed with an alias, without touching any observation.
The country decides where the text is looked up. It is taken from, in order: the block's country; the shape of the postal code where it belongs to one country only (1234 AB is Dutch, SW1A 1AA British); the country of an ip block on the same activity; the region of the block's locale; the owner's country. Without any, the postal code is dropped (a code alone names districts in a dozen countries) and the city is looked up worldwide.
What the Weeztix mapper sends
For every order: a home block for the buyer from the address answers (under their standard names or the shop's own, such as "Woonplaats" or "Postleitzahl"), with the order's locale, and an ip block from the purchaser's IP; for a personalised ticket, a home block for the holder. A new supplier's mapper fills the same blocks.