---
title: "Live event invites for studios | Snowplow Signals"
description: "Snowplow Signals tells your game and messaging tool who has entered the event, how much they have played and when they play, so the invite lands in time."
source: https://signals.snowplow.io/solutions/gaming/live-events
---

[Gaming](https://signals.snowplow.io/solutions/gaming) / Live event invites 

# Invite the player while they are still in the lobby. 

Signals tells your game and your messaging tool whether each player has entered the event, how many matches they have played since it opened and when they usually play, so the invite reaches them between matches or at their usual hour.

[Start free](https://signals.snowplow.io/start) [Talk to us](https://signals.snowplow.io/start?intent=talk) 

Higher event participation

## Get more players into the event by inviting them while they are still playing. 

Participation moves when the invite reaches a player who is in the game, a match just finished and the event untouched. Signals gives your game and your messaging tool whether each player has entered the event, how many event matches they have played, how many matches they have played this session and when they usually play. Your lobby decides what to show, and your messaging tool decides who gets a push and when.

Four ways studios use it

01 Players who finished a match with the event untouched Third match of the night, event untouched. Show the invite in the lobby. 02 Players who entered once and stopped Entered on day one, no event match since. Remind them where they stand. 03 Regulars who have not logged in since the event opened Plays most evenings, nothing since the event opened. One push, at their hour. 04 Players two matches short of an event reward Eight event matches, reward at ten. Say so in the lobby. 

[Read the docs →](https://docs.snowplow.io/docs/signals/introduction/) 

### Show the invite in the lobby before they log off

A player has just finished their third match of the session and has not entered the event that opened this week. The invite goes out on a schedule, as a push or an email, and it tends to reach them after they have logged off for the night. The lobby they are about to see is the better place for it, with the event one tap away and the player still in the mood for another match.

What Signals knows

`matches_session` 

Matches they have finished this session

`event_entries` 

Times they have entered the event since it opened

What your game and messaging tool do

Shows the event invite on the lobby screen when a player has finished a match this session and has no entry to the event, and tells your messaging tool the invite was shown so the scheduled push skips them.

Define it · PythonUse it · TypeScript

game-server.ts · 12 lines Copy Copied 

```
from datetime import timedelta
from snowplow_signals import Attribute, Event, StreamAttributeGroup, user_id

match_ended = Event(name="match_ended", vendor="com.example", version="1-0-0")
event_entered = Event(name="event_entered", vendor="com.example", version="1-0-0")

# Keyed on the player, so a rolling two hours stands in for the session.
matches_session = Attribute(
    name="matches_session",
    type="int32",
    events=[match_ended],
    aggregation="counter",
    period=timedelta(hours=2),
    default_value=0,
)

# The event runs for two weeks, so the window is the event window.
event_entries = Attribute(
    name="event_entries",
    type="int32",
    events=[event_entered],
    aggregation="counter",
    period=timedelta(days=14),
    default_value=0,
)

game_events_lobby = StreamAttributeGroup(
    name="game_events_lobby",
    version=1,
    attribute_key=user_id,
    owner="you@example.com",
    attributes=[matches_session, event_entries],
)
```

```
// In the server code that builds the lobby after a match
const player = await signals.getServiceAttributes({
  name: "game_events_lobby",
  attribute_key: "user_id",
  identifier: playerId,
});
// { matches_session: 3, event_entries: 0 }

if (player.matches_session >= 1 && player.event_entries === 0) {
  lobby.show({ card: "event_invite", event: "spring_cup" });
  messaging.suppress({ campaign: "event_invite_push", playerId });
}
```

### Remind them where they stand, not that the event exists

A player entered the event the day it opened, played one event match and has queued for ranked ever since. The reminder they get is the same invite everyone gets, so it tells them about an event they entered a week ago. What would bring them back is their standing, their place in the table and the matches left to climb it, shown in the lobby when they come back from a ranked match.

What Signals knows

`event_entries` 

Times they have entered the event since it opened

`event_matches` 

Event matches they have played since it opened

`days_since_event_match` 

Days since their last event match

What your game and messaging tool do

Swaps the general invite for a standing card when a player has an entry and no event match in the last three days, filling in the rank from your own leaderboard, and keeps the general invite for the player with no entry at all.

Define it · PythonUse it · TypeScript

game-server.ts · 12 lines Copy Copied 

```
from datetime import timedelta
from snowplow_signals import Attribute, Event, StreamAttributeGroup, user_id

event_entered = Event(name="event_entered", vendor="com.example", version="1-0-0")
event_match_played = Event(name="event_match_played", vendor="com.example", version="1-0-0")

event_entries = Attribute(
    name="event_entries",
    type="int32",
    events=[event_entered],
    aggregation="counter",
    period=timedelta(days=14),
    default_value=0,
)

event_matches = Attribute(
    name="event_matches",
    type="int32",
    events=[event_match_played],
    aggregation="counter",
    period=timedelta(days=14),
    default_value=0,
)

days_since_event_match = Attribute(
    name="days_since_event_match",
    type="double",
    events=[event_match_played],
    aggregation="time_since_last",
    time_unit="d",
)

game_events_standing = StreamAttributeGroup(
    name="game_events_standing",
    version=1,
    attribute_key=user_id,
    owner="you@example.com",
    attributes=[event_entries, event_matches, days_since_event_match],
)
```

```
// In the server code that builds the lobby after a match
const player = await signals.getServiceAttributes({
  name: "game_events_standing",
  attribute_key: "user_id",
  identifier: playerId,
});
// { event_entries: 1, event_matches: 1, days_since_event_match: 6.4 }

if (player.event_entries > 0 && player.days_since_event_match >= 3) {
  const rank = await leaderboard.rankFor(playerId, "spring_cup");
  lobby.show({ card: "event_standing", rank, matchesPlayed: player.event_matches });
}
```

### Send one push at the hour they usually play

A player who plays most evenings has not opened the game since the event opened four days ago. The event push goes out at launch time to everyone, and for this player that is the middle of a workday, so it is swiped away with the rest of the morning's notifications. One push, on a day they are missing and at the hour they usually pick the game up, is a different message, and one is enough.

What Signals knows

`usual_hour_30d` 

The hour of day they have played most in the last 30 days

`days_since_session` 

Days since their last session, counted from the last match they finished

`event_entries` 

Times they have entered the event since it opened

What your game and messaging tool do

Schedules a single push in your messaging tool for the player's usual hour once a regular has been away since the event opened and has no entry, and sends nothing to the player who has been in the game since it opened.

Define it · PythonUse it · TypeScript

push-scheduler.ts · 15 lines Copy Copied 

```
from datetime import timedelta
from snowplow_signals import Attribute, AtomicProperty, Event, StreamAttributeGroup, user_id

match_ended = Event(name="match_ended", vendor="com.example", version="1-0-0")
event_entered = Event(name="event_entered", vendor="com.example", version="1-0-0")

# The hour is taken from each match's timestamp before the count, so the
# value is the hour they play most, not the time of any one match.
usual_hour_30d = Attribute(
    name="usual_hour_30d",
    type="int32",
    events=[match_ended],
    aggregation="most_frequent",
    property=AtomicProperty(name="derived_tstamp", date_part="hour_of_day"),
    period=timedelta(days=30),
)

days_since_session = Attribute(
    name="days_since_session",
    type="double",
    events=[match_ended],
    aggregation="time_since_last",
    time_unit="d",
)

event_entries = Attribute(
    name="event_entries",
    type="int32",
    events=[event_entered],
    aggregation="counter",
    period=timedelta(days=14),
    default_value=0,
)

game_events_usual_hour = StreamAttributeGroup(
    name="game_events_usual_hour",
    version=1,
    attribute_key=user_id,
    owner="you@example.com",
    attributes=[usual_hour_30d, days_since_session, event_entries],
)
```

```
// In the job that schedules event pushes in your messaging tool
const player = await signals.getServiceAttributes({
  name: "game_events_usual_hour",
  attribute_key: "user_id",
  identifier: playerId,
});
// { usual_hour_30d: 20, days_since_session: 4.2, event_entries: 0 }

if (player.days_since_session >= eventOpenedDaysAgo && player.event_entries === 0) {
  messaging.scheduleOnce({
    campaign: "event_invite_push",
    playerId,
    sendAtHour: player.usual_hour_30d,
  });
}
```

### Tell them how close the reward is while they are in the lobby

A player has played eight event matches and the reward unlocks at ten. The progress bar is on the event screen, two taps away, and from the lobby the player heads back to ranked without seeing it. The two matches that finish the reward are the easiest ones to ask for, and the ask belongs on the lobby screen between matches, where the event queue is one tap away.

What Signals knows

`event_matches` 

Event matches they have played since it opened

`matches_session` 

Matches they have finished this session

What your game and messaging tool do

Shows a two-matches-to-go line on the lobby card when a player's event matches are within two of the reward threshold your live ops team set, and points the play button at the event queue for the next match.

Define it · PythonUse it · TypeScript

game-server.ts · 13 lines Copy Copied 

```
from datetime import timedelta
from snowplow_signals import Attribute, Event, StreamAttributeGroup, user_id

match_ended = Event(name="match_ended", vendor="com.example", version="1-0-0")
event_match_played = Event(name="event_match_played", vendor="com.example", version="1-0-0")

event_matches = Attribute(
    name="event_matches",
    type="int32",
    events=[event_match_played],
    aggregation="counter",
    period=timedelta(days=14),
    default_value=0,
)

matches_session = Attribute(
    name="matches_session",
    type="int32",
    events=[match_ended],
    aggregation="counter",
    period=timedelta(hours=2),
    default_value=0,
)

game_events_reward = StreamAttributeGroup(
    name="game_events_reward",
    version=1,
    attribute_key=user_id,
    owner="you@example.com",
    attributes=[event_matches, matches_session],
)
```

```
// In the server code that builds the lobby after a match
const player = await signals.getServiceAttributes({
  name: "game_events_reward",
  attribute_key: "user_id",
  identifier: playerId,
});
// { event_matches: 8, matches_session: 2 }

const remaining = REWARD_THRESHOLD - player.event_matches;
if (remaining > 0 && remaining <= 2 && player.matches_session >= 1) {
  lobby.show({ card: "event_reward_progress", remaining });
  lobby.setPlayQueue("spring_cup");
}
```

## Try it on your own site. 

Add the SDK, define one thing you want to know about a visitor, and watch it update while you click around. The free tier runs on the real engine: no card, no sales call.

[Start free](https://signals.snowplow.io/start) [Talk to an engineer](https://signals.snowplow.io/start?intent=engineer)
