# Publishers

**URL:** https://heroiclabs.com/docs/hiro/concepts/publishers/
**Summary:** Learn about the Publishers system for processing analytics events in Hiro
**Keywords:** publishers, analytics, events, monitoring
**Categories:** hiro, publishers, concepts

---


# Publishers

Publishers describe a service that receives and processes events generated server-side by various Hiro systems, such as achievements, economy, teams, and more. They act as listeners that respond to events, enabling you to implement workflows like custom analytics tracking, custom rewards, or integrating with external services.

## Use cases

Publishers can be used to extend the capabilities of existing Hiro systems with custom workflows that go beyond the standard mechanisms.

### Extending existing Hiro systems

The [Rewards system](../economy/rewards) grants players currencies, energies, and items. Publishers handle scenarios it doesn't cover, like updating player stats when events occur.

This Publisher grants experience points when a player claims an achievement:

```go
// ExpRewardPublisher grants experience points when achievements are claimed
type ExpRewardPublisher struct {
    systems hiro.Hiro
}

// Ensure ExpRewardPublisher implements the Publisher interface
var _ hiro.Publisher = (*ExpRewardPublisher)(nil)

// Send processes events and grants exp for achievement claims
func (p *ExpRewardPublisher) Send(ctx context.Context, logger runtime.Logger,
nk runtime.NakamaModule, userID string, events []*hiro.PublisherEvent) {
    for _, event := range events {
        // Only process achievement claim events
        if event.Name != "achievementClaimed" {
            continue
        }

        // Look up the achievement configuration
        achievements, _, _ := p.systems.GetAchievementsSystem().GetAchievements(ctx, logger, nk, userID)

        // Find the achievement that was claimed
        for _, achievement := range achievements {
            if achievement.Id != event.SourceId {
                continue
            }

            // Check if this achievement rewards exp
            if expStr, ok := achievement.AdditionalProperties["reward_exp"]; ok {
                exp, _ := strconv.ParseInt(expStr, 10, 64)

                // Update the player's exp stat
                p.systems.GetStatsSystem().Update(ctx, logger, nk, userID, nil, []*hiro.StatUpdate{{
                    Name:     "exp",
                    Value:    exp,
                    Operator: hiro.StatUpdateOperator_STAT_UPDATE_OPERATOR_DELTA,
                }})
            }
            break
        }
    }
}


// This publisher doesn't need to act on authentication, so it's a no-op.
func (p *ExpRewardPublisher) Authenticate(ctx context.Context, logger runtime.Logger,
    nk runtime.NakamaModule, userID string, created bool) {}
```

Register the publisher after initializing your Hiro systems:

```go
systems.AddPublisher(&ExpRewardPublisher{systems: systems})
```

### Analytics and LiveOps

Publishers can be used for sending gameplay events to analytics platforms. Hiro's built-in [`SatoriPersonalizer`](#satoripersonalizer) handles this for [Satori](../../../satori/), publishing events like achievement claims, purchases, and energy usage that Satori uses for player segmentation and targeting. You can register additional Publishers to send events to your own analytics backend or third-party services alongside `SatoriPersonalizer`.

## Built-in publishers

### SatoriPersonalizer

[`SatoriPersonalizer`](https://github.com/heroiclabs/hiro/blob/main/personalizer_satori.go) is Hiro's built-in Publisher implementation. It implements both the Publisher and [Personalizer](../personalizers/) interfaces, creating a bidirectional adapter between Hiro and Satori:

| Role         | Direction         | Purpose                                                                                                                                                             |
| ------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Publisher    | Writes to Satori  | Sends analytics events                                                                                                                                              |
| Personalizer | Reads from Satori | Fetches [remote config](../../../satori/concepts/remote-configuration/) and [live event](../../../satori/concepts//live-events/) data to customize gameplay configs |

This two-way flow is key to Satori's LiveOps capabilities. As a Publisher, it sends player events (achievements claimed, purchases made, etc.) to Satori. Satori can then use this behavioral data to segment players and deliver targeted configuration back through [feature flags](../../../satori/concepts/remote-configuration/understand-feature-flags/), which `SatoriPersonalizer` reads as a Personalizer.

When you initialize `SatoriPersonalizer`, you must specify which events you want to publish by passing them in. For example, to publish authenticate, achievements, economy, and energy events:

```go
systems.AddPersonalizer(hiro.NewSatoriPersonalizer(ctx,
    hiro.SatoriPersonalizerPublishAuthenticateEvents(),
    hiro.SatoriPersonalizerPublishAchievementsEvents(),
    hiro.SatoriPersonalizerPublishEconomyEvents(),
    hiro.SatoriPersonalizerPublishEnergyEvents(),
))
```

To publish all events, pass in `hiro.SatoriPersonalizerPublishAllEvents()`. This uses the "no session" option for authentication by default. If you don't want to publish any events (that is, to use `SatoriPersonalizer` solely for its Personalizer functionality), pass in `ctx` alone.

{{< note "important" "AddPersonalizer() vs AddPublisher()" >}}
Pass `SatoriPersonalizer` to `AddPersonalizer()` to instruct Hiro to automatically register it as both a Personalizer and Publisher. Use `AddPublisher()` to register your own custom publisher implementations. You can register multiple publishers; events are sent to all of them.
{{< /note >}}

#### Authentication options

There are two options for publishing authentication events to Satori:

| Option       | Parameter                                                  | Behavior                                                           |
| ------------ | ---------------------------------------------------------- | ------------------------------------------------------------------ |
| no session   | `SatoriPersonalizerPublishAuthenticateEvents()`            | Authenticates without creating a server-side session (recommended) |
| with session | `SatoriPersonalizerPublishAuthenticateEventsWithSession()` | Creates a new random session ID for each authentication            |

These options authenticate the player to Satori immediately after Nakama [authentication](../../../nakama/concepts/authentication/), creating them in Satori if they don't already exist. This is essential if you want to fetch feature flags for new players — without one of these options, `SatoriPersonalizer` cannot retrieve flags for players who haven't been created in Satori yet.

{{< note "important" "Avoid creating multiple session IDs" >}}
If your game client also authenticates with Satori directly, use `SatoriPersonalizerPublishAuthenticateEvents()`. Using `SatoriPersonalizerPublishAuthenticateEventsWithSession()` will generate two `_sessionStart` events (one from the client, one from Nakama) with two different session IDs.

With the "no session" option, Nakama authenticates with Satori without creating a new session ID; instead, all server-side events are automatically associated with the client's session when they reach Satori.

Read more about [Sessions](../../../satori/concepts/performance-monitoring/manage-sessions/).
{{< /note >}}

## Publisher interface

The Publisher interface defines two methods that must be implemented:

```go
type Publisher interface {
    // Authenticate is called every time a user authenticates with Hiro.
    // The 'created' flag is true if this is a newly created user account.
    Authenticate(ctx context.Context, logger runtime.Logger,
        nk runtime.NakamaModule, userID string, created bool)

    // Send is called when there are one or more events generated.
    Send(ctx context.Context, logger runtime.Logger,
        nk runtime.NakamaModule, userID string, events []*PublisherEvent)
}
```

## PublisherEvent structure

The PublisherEvent structure contains information about an event:

```go
type PublisherEvent struct {
    Name      string            // Event name
    Id        string            // Unique event identifier
    Timestamp int64             // Unix timestamp
    Metadata  map[string]string // Additional event data
    Value     string            // Event value
    System    System            // The Hiro system that generated this event
    SourceId  string            // Identifier of the event source
    Source    any               // Configuration of the event source
}
```

## Related resources

- [Personalizers](../personalizers/)
- [Analytics in Unity](../../unity/analytics/)
- [Satori Sessions](../../../satori/concepts/performance-monitoring/manage-sessions/)
- [Set up Personalizers](/docs/hiro/guides/personalizer/setup-personalizers/)
