Skip to content

Webhook Payloads Full Closed Beta

Complete payload schema reference for loyalty egress webhook deliveries. For a guided introduction, see the Webhooks guide and Resolution Modes.

Closed Beta

Webhook payloads are part of the Closed Beta loyalty integration. Schemas described here are subject to change. Contact us for access.

Delivery Format

Every webhook delivery is an HTTP POST with:

  • Content-Type: application/json
  • Body: A single JSON object matching one of the three resolution mode schemas below
  • Headers: See Webhook Headers

Delivery Headers

HeaderTypeDescription
Content-Typeapplication/jsonAlways JSON
X-FanFest-Signaturet=<unix_ts>,v1=<hex_hmac>HMAC-SHA256 signature (see verification)
X-FanFest-Timestampstring (Unix seconds)When the signature was generated
X-FanFest-Event-Idstring (UUID)Unique event ID for idempotency

TypeScript Interfaces

Union Type

typescript
type EgressPayload =
  | AggregatedEgressPayload
  | DayAggregatedEgressPayload
  | HighFidelityEgressPayload;

All three interfaces share the resolution field as a discriminator. Use it to narrow the type:

typescript
function handleWebhook(payload: EgressPayload): void {
  switch (payload.resolution) {
    case 'aggregated':
      console.log(`Total points: ${payload.total_points}`);
      break;
    case 'day_aggregated':
      console.log(`Points on ${payload.date}: ${payload.points}`);
      break;
    case 'high_fidelity':
      console.log(`Action ${payload.user_action_id}: ${payload.points} pts`);
      break;
  }
}

AggregatedEgressPayload

All-time totals per user, per channel, per action.

typescript
interface AggregatedEgressPayload {
  /** Discriminator — always "aggregated" */
  resolution: 'aggregated';

  /** FanFest channel ID */
  channel_id: string;

  /** User who performed the action */
  user_id: string;

  /** Action type (e.g., "quiz_answer", "poll_vote") */
  app_action_name: string;

  /** Sum of all settled points for this user + channel + action (all time) */
  total_points: number;

  /** Total number of times this action was performed */
  total_occurrences: number;

  /** Community IDs associated with the triggering action */
  community_ids: string[];

  /** ISO 8601 timestamp of when the payload was generated */
  timestamp: string;
}

Example payload:

json
{
  "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"
}

DayAggregatedEgressPayload

Daily bucketed totals per user, per channel, per action.

typescript
interface DayAggregatedEgressPayload {
  /** Discriminator — always "day_aggregated" */
  resolution: 'day_aggregated';

  /** FanFest channel ID */
  channel_id: string;

  /** User who performed the action */
  user_id: string;

  /** Action type */
  app_action_name: string;

  /** UTC date in YYYY-MM-DD format */
  date: string;

  /** Sum of settled points for this user + channel + action on this UTC day */
  points: number;

  /** Number of times this action was performed on this UTC day */
  occurrences: number;

  /** Community IDs associated with the triggering action */
  community_ids: string[];
}

Example payload:

json
{
  "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"]
}

HighFidelityEgressPayload

Individual action records with full detail.

typescript
interface HighFidelityEgressPayload {
  /** Discriminator — always "high_fidelity" */
  resolution: 'high_fidelity';

  /** FanFest channel ID */
  channel_id: string;

  /** User who performed the action */
  user_id: string;

  /** Unique identifier for this specific action record */
  user_action_id: string;

  /** Action type */
  app_action_name: string;

  /** Points awarded for this specific action (sum of settled transactions) */
  points: number;

  /** Occurrences recorded for this action (typically 1) */
  occurrences: number;

  /** Community IDs associated with this action */
  community_ids: string[];

  /** ISO 8601 timestamp of when the action was originally recorded */
  created_at: string;
}

Example payload:

json
{
  "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

Common Fields (All Modes)

FieldTypeDescription
resolutionstringDiscriminator: "aggregated", "day_aggregated", or "high_fidelity"
channel_idstringThe FanFest channel ID
user_idstringThe user who performed the loyalty action
app_action_namestringThe action type identifier (e.g., "quiz_answer", "poll_vote", "referral", "check_in")
community_idsstring[]Community IDs associated with the action

Aggregated-Only Fields

FieldTypeDescription
total_pointsnumberSum of all settled points for this user + channel + action (all time)
total_occurrencesnumberTotal number of times this action was performed
timestampstringISO 8601 — when the payload was generated (not when the action occurred)

Day Aggregated-Only Fields

FieldTypeDescription
datestringUTC date in YYYY-MM-DD format
pointsnumberSum of settled points for this day
occurrencesnumberNumber of occurrences for this day

High Fidelity-Only Fields

FieldTypeDescription
user_action_idstringUnique ID for this action record — use for deduplication
pointsnumberPoints for this specific action
occurrencesnumberOccurrences for this specific action (typically 1)
created_atstringISO 8601 — when the action was originally recorded

Validation Notes

  • All points fields include only settled transactions. On-hold transactions are excluded.
  • total_points and points are integers (no fractional points).
  • community_ids may be an empty array if the action has no community associations.
  • app_action_name is never null in webhook payloads — if the underlying data has no action name, it is delivered as an empty string.
  • timestamp (aggregated mode) and created_at (high fidelity mode) are always valid ISO 8601 strings with timezone designator (Z).
  • date (day aggregated mode) is always in YYYY-MM-DD format using UTC.

Released under the MIT License.