Resolution Modes Full Closed Beta
Every channel's egress configuration specifies a resolution mode that controls how loyalty data is structured in both webhook payloads and REST API responses. The three modes form a spectrum from simple totals to full individual records.
Closed Beta
Resolution modes are part of the Closed Beta loyalty integration. Payload schemas described here are subject to change. Contact us for access.
Overview
All three modes produce payloads that are discriminated unions on the resolution field. Your code can branch on this field to handle each mode:
function handlePayload(payload: EgressPayload) {
switch (payload.resolution) {
case 'aggregated':
// payload.total_points, payload.total_occurrences
break;
case 'day_aggregated':
// payload.date, payload.points, payload.occurrences
break;
case 'high_fidelity':
// payload.user_action_id, payload.created_at
break;
}
}Aggregated
All-time totals per user, per channel, per action. The simplest mode — each payload summarizes the running total of points and occurrences.
When to use: Point balance displays, leaderboard sync, simple dashboards. Sufficient for most integrations.
Payload Schema
interface AggregatedEgressPayload {
resolution: 'aggregated';
channel_id: string;
user_id: string;
app_action_name: string;
total_points: number;
total_occurrences: number;
community_ids: string[];
timestamp: string; // ISO 8601
}Example
{
"resolution": "aggregated",
"channel_id": "ch_abc123",
"user_id": "usr_xyz789",
"app_action_name": "quiz_answer",
"total_points": 1250,
"total_occurrences": 47,
"community_ids": ["com_111", "com_222"],
"timestamp": "2025-06-15T14:32:00.000Z"
}Field Reference
| Field | Type | Description |
|---|---|---|
resolution | "aggregated" | Discriminator — always "aggregated" for this mode |
channel_id | string | The FanFest channel ID |
user_id | string | The user who performed the action |
app_action_name | string | The action type (e.g., quiz_answer, poll_vote, referral) |
total_points | number | Sum of all settled points for this user + channel + action (all time) |
total_occurrences | number | Total number of times this action was performed |
community_ids | string[] | Community IDs associated with the triggering action |
timestamp | string | ISO 8601 timestamp of when the payload was generated |
Details
total_pointsincludes only settled transactions. Transactions withon_holdstatus are excluded.total_occurrencescounts all occurrences regardless of transaction status.timestampreflects when the payload was computed, not when the action occurred.- In REST API responses (pull),
community_idsreturns an empty array for aggregated results because community data is not available from grouped database queries.
Day Aggregated
Daily bucketed totals per user, per channel, per action. Each payload summarizes the points and occurrences for a single UTC day.
When to use: Daily reconciliation, time-series reporting, syncing daily activity to external systems.
Payload Schema
interface DayAggregatedEgressPayload {
resolution: 'day_aggregated';
channel_id: string;
user_id: string;
app_action_name: string;
date: string; // "YYYY-MM-DD" (UTC)
points: number;
occurrences: number;
community_ids: string[];
}Example
{
"resolution": "day_aggregated",
"channel_id": "ch_abc123",
"user_id": "usr_xyz789",
"app_action_name": "quiz_answer",
"date": "2025-06-15",
"points": 75,
"occurrences": 3,
"community_ids": ["com_111"]
}Field Reference
| Field | Type | Description |
|---|---|---|
resolution | "day_aggregated" | Discriminator — always "day_aggregated" for this mode |
channel_id | string | The FanFest channel ID |
user_id | string | The user who performed the action |
app_action_name | string | The action type |
date | string | UTC date in YYYY-MM-DD format |
points | number | Sum of settled points for this user + channel + action on this UTC day |
occurrences | number | Number of times this action was performed on this UTC day |
community_ids | string[] | Community IDs associated with the triggering action |
Details
- Day boundaries are determined by UTC, not your local timezone. An action at
2025-06-15T23:59:00Zbelongs to2025-06-15, while an action at2025-06-16T00:01:00Zbelongs to2025-06-16. pointsincludes only settled transactions for the specified day.- In REST API responses (pull),
community_idsreturns an empty array for day-aggregated results.
High Fidelity
Individual action records with full detail. Each payload represents a single user action exactly as it was recorded.
When to use: Audit trails, real-time event streaming, detailed analytics, compliance requirements.
Payload Schema
interface HighFidelityEgressPayload {
resolution: 'high_fidelity';
channel_id: string;
user_id: string;
user_action_id: string;
app_action_name: string;
points: number;
occurrences: number;
community_ids: string[];
created_at: string; // ISO 8601
}Example
{
"resolution": "high_fidelity",
"channel_id": "ch_abc123",
"user_id": "usr_xyz789",
"user_action_id": "ua_def456",
"app_action_name": "quiz_answer",
"points": 25,
"occurrences": 1,
"community_ids": ["com_111", "com_222"],
"created_at": "2025-06-15T14:32:00.000Z"
}Field Reference
| Field | Type | Description |
|---|---|---|
resolution | "high_fidelity" | Discriminator — always "high_fidelity" for this mode |
channel_id | string | The FanFest channel ID |
user_id | string | The user who performed the action |
user_action_id | string | Unique identifier for this specific action record |
app_action_name | string | The action type |
points | number | Points awarded for this specific action (sum of settled transactions) |
occurrences | number | Occurrences recorded for this action (typically 1) |
community_ids | string[] | Community IDs associated with this action |
created_at | string | ISO 8601 timestamp of when the action was originally recorded |
Details
user_action_iduniquely identifies this action and can be used for deduplication on your side.pointsis the sum of settled transactions associated with this specific action.community_idsis fully populated in high fidelity mode (both push and pull paths).- This is the only mode where
created_atreflects the actual action timestamp (not the payload generation time).
Comparison Table
| Feature | Aggregated | Day Aggregated | High Fidelity |
|---|---|---|---|
| Data granularity | All-time totals | Daily buckets | Individual actions |
| Payload size | Smallest | Medium | Largest |
community_ids in pull API | Empty array | Empty array | Populated |
| Includes action timestamp | No (generation time only) | No (date only) | Yes (created_at) |
Includes user_action_id | No | No | Yes |
| DB queries required | 2 (aggregation) | 2 (aggregation) | 0 (pass-through) |
| Recommended for | Display, sync | Reconciliation, reporting | Audit, analytics |
Choosing a Resolution Mode
Recommendation
Start with Aggregated if you are unsure. It is the simplest to consume, produces the smallest payloads, and is sufficient for displaying point balances and syncing leaderboard data. You can change your resolution mode later without modifying your webhook endpoint or API client — only the payload shape changes.
Upgrade to Day Aggregated if you need to reconcile daily activity between FanFest and your system, or if you produce time-series reports.
Upgrade to High Fidelity if you need a complete audit trail of every individual action, or if you are building real-time analytics on top of FanFest loyalty data.
TypeScript Types
For TypeScript projects, you can use these type definitions to handle all three resolution modes:
interface AggregatedEgressPayload {
resolution: 'aggregated';
channel_id: string;
user_id: string;
app_action_name: string;
total_points: number;
total_occurrences: number;
community_ids: string[];
timestamp: string;
}
interface DayAggregatedEgressPayload {
resolution: 'day_aggregated';
channel_id: string;
user_id: string;
app_action_name: string;
date: string;
points: number;
occurrences: number;
community_ids: string[];
}
interface HighFidelityEgressPayload {
resolution: 'high_fidelity';
channel_id: string;
user_id: string;
user_action_id: string;
app_action_name: string;
points: number;
occurrences: number;
community_ids: string[];
created_at: string;
}
type EgressPayload =
| AggregatedEgressPayload
| DayAggregatedEgressPayload
| HighFidelityEgressPayload;The discriminated union on resolution gives you exhaustive type narrowing in switch statements and if checks.
