# Events

**URL:** https://heroiclabs.com/docs/nakama/concepts/events/
**Summary:** Events let you capture player behavior and analytics data once in the client, then route and process it server-side, so you avoid embedding multiple third-party SDKs into your game.
**Keywords:** generate events, send events, create events, built-in events, process events
**Categories:** nakama, events, concepts

---


# Events

Most games need to send behavioral data and analytics to services like ad networks, analytics platforms, and purchase verification systems. Typically, each service requires its own SDK in the game client, adding download size, network overhead, and ongoing maintenance burden.

Events let you capture this data once and route it server-side. Your game client sends lightweight event data to Nakama, which processes it in the background and forwards it to whichever third-party services you've configured. Players experience minimal interruption, and you avoid embedding multiple SDKs into your client.

This approach gives you three practical benefits: a smaller game client (which improves your first-time user experience and download conversion), less network traffic from the client to external services, and significantly lower integration and maintenance cost for your team.

{{< note "important" "" >}}
Internally, Nakama processes events using a high-performance circular buffer and a pool of worker goroutines. This means large volumes of events won't overload your server even if your event handlers are slower than the ingestion rate. See [Advanced settings](#advanced-settings) to tune the queue size and worker count.
{{</ note >}}

## Generate Events

Events can be sent to the server by game clients or created on the server. An event has a name and a map of properties which decorate the event with more information.

### Send Events

Use the event API to send to the server.

{{< code type="client" >}}

```bash
curl -vvv -H "Authorization: Bearer $SESSION"  http://127.0.0.1:7350/v2/event -d '{"name": "my_event", "properties": {"my_key": "my_value"}}'
```

{{< / code >}}

{{< code type="client" framework="godot3" >}}

```gdscript
yield(client.event_async(session, "my_event", {"my_key": "my_value"}), "completed")
```

{{< / code >}}

{{< code type="client" framework="godot4" >}}

```gdscript
await client.event_async(session, "my_event", {"my_key": "my_value"})
```

{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}

```lua
local external = true
local name = "my_event"
local properties = { my_key = "my_value" }
local result = nakama.event(client, external, name, properties)
```

{{< / code >}}

{{< missing type="client" lang="csharp" />}}
{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="javascript" />}}
{{< missing type="client" lang="java" />}}
{{< missing type="client" lang="shell" />}}
{{< missing type="client" lang="swift" />}}

### Create Events

Use the server side module to generate events in your Go code.

{{< code type="server" >}}

```go
// import "github.com/heroiclabs/nakama-common/api"
// import "github.com/heroiclabs/nakama-common/runtime"

// ctx context.Context, nk runtime.NakamaModule
evt := &api.Event{
    Name:       "event_name"
    Properties: map[string]string{
       "my_key": "my_value",
    },
    External:   true,
}
if err := nk.Event(ctx, evt); err != nil {
    // Handle error.
}
```

{{< / code >}}

{{< code type="server" >}}

```lua
local nk = require("nakama")

local properties = {
  my_key = "my_value"
}
local timestamp = nk.time
local external = false

nk.event("event_name", properties, timestamp, external)
```

{{< / code >}}

{{< code type="server" >}}

```typescript
let properties = {
  my_key: "my_value",
};

nk.event("event_name", properties);
```

{{< / code >}}

You can also take advantage of [after hooks](../../server-framework/introduction/hooks/#after-hooks) in the server runtime when you want to send events from other features in the server. For example when a user account is updated and you might want to send an event to be processed.

{{< code type="server" >}}

```go
func afterUpdateAccount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.UpdateAccountRequest) error {
    evt := &api.Event{
        Name:       "account_updated",
        Properties: map[string]string{},
        External:   false, // used to denote if the event was generated from the client
    }
    return nk.Event(context.Background(), evt)
}

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterAfterUpdateAccount(afterUpdateAccount); err != nil {
        return err
    }
    return nil
}
```

{{< / code >}}

{{< code type="server" >}}

```lua
local nk = require("nakama")

local function after_update_account(ctx, payload)
  local properties = {
    my_key = "my_value"
  }
  local timestamp = nk.time
  local external = false  -- used to denote if the event was generated from the client
  return nk.event("event_name", properties, timestamp, external)
end

nk.register_req_after(after_update_account, "UpdateAccount")
```

{{< / code >}}

{{< code type="server" >}}

```typescript
function InitModule(
  ctx: nkruntime.Context,
  logger: nkruntime.Logger,
  nk: nkruntime.Nakama,
  initializer: nkruntime.Initializer,
) {
  let afterUpdateAccount: nkruntime.AfterHookFunction<
    void,
    nkruntime.UserUpdateAccount
  > = function (
    ctx: nkruntime.Context,
    logger: nkruntime.Logger,
    nk: nkruntime.Nakama,
    data: void,
    payload: nkruntime.UserUpdateAccount,
  ) {
    let properties = {
      my_key: "my_value",
    };

    nk.event("event_name", properties);
  };
  initializer.registerAfterUpdateAccount(afterUpdateAccount);
}
```

{{< / code >}}

### Built-in Events

The server will generate built-in events which can be processed specifically for the **SessionStart** and **SessionEnd** actions. These special events occur when the server has a new socket session start and when it ends. See below for how to process these events.

## Process Events

Events can be processed with a function registered to the runtime initializer at server startup. Events will have its external field marked as `true` if its been generated by clients.

{{< note "important" >}}
Processing events through Lua/TypeScript runtime functions is not yet supported.
{{< / note >}}

{{< code type="server" >}}

```go
func processEvent(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    switch evt.GetName() {
    case "account_updated":
        logger.Debug("process evt: %+v", evt)
        // Send event to an analytics service.
    default:
       logger.Error("unrecognised evt: %+v", evt)
    }
}

// initializer runtime.Initializer
if err := initializer.RegisterEvent(processEvent); err != nil {
    return err
}
```

{{< / code >}}

{{< missing type="server" lang="typescript" />}}
{{< missing type="server" lang="lua" />}}

### Built-in Events

Events which are internally generated have their own registration functions. The SessionStart is created when a new socket connection is opened on the server.

{{< code type="server" >}}

```go
func eventSessionStart(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    logger.Debug("process event session start: %+v", evt)
}

// initializer runtime.Initializer
if err := initializer.RegisterEventSessionStart(eventSessionStart); err != nil {
    return err
}
```

{{< / code >}}

{{< missing type="server" lang="typescript" />}}
{{< missing type="server" lang="lua" />}}

The SessionEnd event is created when the socket connection for a session is closed. The socket could have closed for a number of reasons but can be observed to react on.

{{< code type="server" >}}

```go
func eventSessionEnd(ctx context.Context, logger runtime.Logger, evt *api.Event) {
   logger.Debug("process event session end: %+v", evt)
}

// initializer runtime.Initializer
if err := initializer.RegisterEventSessionEnd(eventSessionEnd); err != nil {
   return err
}
```

{{< / code >}}

{{< missing type="server" lang="typescript" />}}
{{< missing type="server" lang="lua" />}}

## Advanced Settings

To protect the performance of the server the event processing subsystem is designed to limit the resources allocated to process events. You can adjust the resources allocated to this subsystem with these configuration settings.

| Configuration Key           | Value | Description                                                                  |
| --------------------------- | ----- | ---------------------------------------------------------------------------- |
| runtime.event_queue_size    | int   | Size of the event queue buffer. Defaults to 65536.                           |
| runtime.event_queue_workers | int   | Number of workers to use for concurrent processing of events. Defaults to 8. |

To review other configuration settings, see [Server configuration](../../getting-started/configuration/).

## Example

This is a complete sample plugin which processes SessionStart, SessionEnd, and events generated when a user account is updated.

```go
package main

import (
    "context"
    "database/sql"
    "github.com/heroiclabs/nakama-common/api"
    "github.com/heroiclabs/nakama-common/runtime"
)

func afterUpdateAccount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.UpdateAccountRequest) error {
    evt := &api.Event{
        Name:       "account_updated",
        Properties: map[string]string{},
        External:   false,
    }
    return nk.Event(context.Background(), evt)
}

func processEvent(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    switch evt.GetName() {
    case "account_updated":
        logger.Debug("process evt: %+v", evt)
        // Send event to an analytics service.
    default:
        logger.Error("unrecognised evt: %+v", evt)
    }
}

func eventSessionEnd(ctx context.Context, logger runtime.Logger, evt *api.Event) {
   logger.Debug("process event session end: %+v", evt)
}

func eventSessionStart(ctx context.Context, logger runtime.Logger, evt *api.Event) {
    logger.Debug("process event session start: %+v", evt)
}

//noinspection GoUnusedExportedFunction
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterAfterUpdateAccount(afterUpdateAccount); err != nil {
       return err
    }
    if err := initializer.RegisterEvent(processEvent); err != nil {
        return err
    }
    if err := initializer.RegisterEventSessionEnd(eventSessionEnd); err != nil {
       return err
    }
    if err := initializer.RegisterEventSessionStart(eventSessionStart); err != nil {
       return err
    }
    logger.Info("Server loaded.")
    return nil
}
```
