Kafka Events Contract (HH-Lion)
This document defines exactly what producers should send to Kafka for HH-Lion.
1) Topic you should produce to
-
Primary ingestion topic:
hh.lion.interaction.events- Purpose: behavioral events used for feature computation, training data, and analytics.
- Controlled by env var
KAFKA_EVENTS_TOPIC(default:hh.lion.interaction.events). - Where
NAMESPACEprefixing is enabled, the effective topic becomes<NAMESPACE>.hh.lion.interaction.events.
-
Do not produce application events directly to
recsys.impressionsunless you explicitly want to override HH-Lion behavior.- HH-Lion emits
homepage_impressiontoKAFKA_IMPRESSION_TOPIC(default:recsys.impressions) fromPOST /v2/homepage.
- HH-Lion emits
-
Dead-letter topic (
hh.lion.interaction.events.dlq) is internal and receives invalid events from the consumer.
2) Required behavioral event types (hh.lion.interaction.events)
You should send these canonical event_type values:
impressionclickviewfavorite_addedpackage_selectedbegin_checkoutbooking_confirmedsearch
Source of truth:
specs/003-clickhouse-feature-store/contracts/interaction_event.avscrecsys/ingestion/quality.py
3) Required data (all fields and constraints)
Always required for every event
| Field | Type | Rules |
|---|---|---|
event_id | string | Must be UUIDv4 |
event_type | enum | Must be one of the 8 event types above |
event_timestamp | long (epoch ms) | Must be int millis; default quality window: not older than 7 days, not more than 10 minutes in future |
anonymous_id | string | Must be Nano ID (21 chars, URL-safe alphabet) |
session_id | string | Must be Nano ID (21 chars, URL-safe alphabet) |
context.language | string | ISO 639-1 (2 lowercase letters), e.g. en, th |
context.device | enum | mobile, tablet, desktop |
context.referrer_category | enum | organic_search, paid_search, social, direct, partner, unknown |
Optional/common fields
| Field | Type | Notes |
|---|---|---|
user_id | string | null | Logged-in member id (null for guests) |
context.geo | string | null | Pattern CC-CITY_ID, e.g. TH-1 |
strategy_id | string | null | Ranking strategy identifier |
experiment_ids | string[] | null | Active experiment ids/variants |
discount | decimal | null | Allowed only for package events (package_selected, begin_checkout, booking_confirmed) |
Conditionally required fields by event type
| Field | Required when | Extra constraints |
|---|---|---|
item_key, item_type, item_id | All item-scoped events: impression, click, view, favorite_added, package_selected, begin_checkout, booking_confirmed | item_key must equal item_type:item_id; item_type enum: restaurant, tag, link |
section_id, section_type, position | impression, click | position >= 1 |
request_id, surface | impression, click, view, search | surface enum: homepage, search, detail, collection, unknown |
package_id | package_selected, begin_checkout, booking_confirmed | item_type must be restaurant |
search_query | search | Max length 256 |
booking_value | booking_confirmed | Decimal, must be > 0 |
price_shown, price_shown_currency, inventory_state, party_size, booking_timestamp | package_selected, begin_checkout | price_shown_currency is ISO 4217 (3 chars); party_size >= 1 |
price_tier, cuisine | booking_confirmed only | price_tier enum: budget, mid, premium |
Important negative rules
- For non-item-scoped events (
search),item_key,item_type, anditem_idmust be null/absent. package_idis only allowed for package events (package_selected,begin_checkout,booking_confirmed).search_queryis only allowed forsearch.booking_valueis only allowed forbooking_confirmed.price_tierandcuisineare only allowed forbooking_confirmed.
Section/item consistency
When section_type is one of:
restaurant_list=>item_typemust berestauranttag_list=>item_typemust betaglink_list=>item_typemust belink
4) Minimal valid payload examples (all required event types)
[
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "impression",
"event_timestamp": 1764200000000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"section_id": "popular_near_you",
"section_type": "restaurant_list",
"position": 1,
"request_id": "req-home-001",
"surface": "homepage",
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440010",
"event_type": "click",
"event_timestamp": 1764200001000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"section_id": "popular_near_you",
"section_type": "restaurant_list",
"position": 1,
"request_id": "req-home-001",
"surface": "homepage",
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440020",
"event_type": "view",
"event_timestamp": 1764200002000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"request_id": "req-detail-001",
"surface": "detail",
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440030",
"event_type": "favorite_added",
"event_timestamp": 1764200003000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440040",
"event_type": "package_selected",
"event_timestamp": 1764200004000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"package_id": "pkg-0001",
"price_shown": "1200.00",
"price_shown_currency": "THB",
"inventory_state": "available",
"party_size": 2,
"booking_timestamp": 1764286400000,
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440050",
"event_type": "begin_checkout",
"event_timestamp": 1764200005000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"package_id": "pkg-0001",
"price_shown": "1200.00",
"price_shown_currency": "THB",
"inventory_state": "available",
"party_size": 2,
"booking_timestamp": 1764286400000,
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440060",
"event_type": "booking_confirmed",
"event_timestamp": 1764200006000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"item_key": "restaurant:1",
"item_type": "restaurant",
"item_id": "1",
"package_id": "pkg-0001",
"booking_value": "1200.00",
"price_tier": "mid",
"cuisine": "japanese",
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "direct" }
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440070",
"event_type": "search",
"event_timestamp": 1764200007000,
"anonymous_id": "V1StGXR8_Z5jdHi6B-myT",
"session_id": "IRFa-VaY2b8A9xQ7mL0Pn",
"request_id": "req-search-001",
"surface": "search",
"search_query": "sushi omakase",
"context": { "geo": "TH-1", "language": "en", "device": "mobile", "referrer_category": "organic_search" }
}
]
Note: JSON examples use decimal values as strings for compatibility with common serializers before Avro encoding.
5) Secondary topic: recsys.impressions (emitted by HH-Lion API)
HH-Lion emits this automatically when homepage results are returned.
Schema source:
specs/018-recsys-audit-remediation/contracts/impression-event.avscrecsys/shared/events.py
Top-level fields:
event_id,event_type,event_versionrequest_id,user_id,anonymous_id,session_idsections[](section_id,section_type,strategy,position,item_count)experiment_assignments[](flag_key,variant,assignment_timestamp)context(geo,language,device,referrer_category)metrics(latency_ms,fallback_triggered,cache_hit)timestamp
6) Producer checklist
- Generate UUIDv4 for
event_id, and Nano ID foranonymous_idandsession_id. - Send epoch milliseconds in
event_timestampandbooking_timestamp. - Ensure
context.language,context.device,context.referrer_categoryare always present. - Enforce event-specific required fields from section 3.
- Use canonical enums exactly as defined above (lowercase).