Handle Multiple Events
Overview
An Extend Event Handler app is a gRPC server. AccelByte Gaming Services (AGS) emits events to Kafka, and the Extend platform delivers each event to your app by calling a gRPC method.
A single Extend Event Handler app can handle as many events as you want. For each event you care about, you:
- Include its Protocol Buffers (Protobuf) definition.
- Implement the gRPC service that the proto generates.
- Register that service with the gRPC server.
There is no special router to configure. Every AGS event maps to its own gRPC service with a single OnMessage RPC, so implementing more services simply means your one app responds to more events. The pattern works identically across Go, Java, Python, and C#. Only the language mechanics differ.
For background, see Get started with Extend Event Handler app template and Create your own Extend Event Handler app.
How event delivery works
Each AGS event is described in a .proto file as a service with one RPC. The message — for example, UserLoggedIn — carries the event payload.
// One service per event. The message (for example, UserLoggedIn) carries the payload.
service UserAuthenticationUserLoggedInService {
rpc OnMessage(UserLoggedIn) returns (google.protobuf.Empty);
}
service UserAuthenticationUserLoggedOutService {
rpc OnMessage(UserLoggedOut) returns (google.protobuf.Empty);
}
The AccelByte event spec proto files (covering account, auth, and user events as well as many other domains) are published in the accelbyte-api-proto repository. When AGS produces a userLoggedIn event, the Extend platform invokes UserAuthenticationUserLoggedInService/OnMessage on your app. If you have implemented and registered that service, your code runs. If you have not, the event is simply not handled.
The recipe to handle multiple events is always the same three steps:
- Add the proto for each event you want, then regenerate the code.
- Implement the generated service by writing the
OnMessagehandler. - Register the service with the gRPC server.
The reference apps ship with UserLoggedIn (and UserThirdPartyLoggedIn) already implemented as examples. You extend the same pattern for every additional event.
Add, implement, and register a service
The steps below show how to apply the three-step pattern in each language. As an example, they add a handler for the userLoggedOut event alongside the existing login handlers.
- C#
- Go
- Java
- Python
Reference: extend-event-handler-csharp
-
Add the proto and reference it. Place the proto under
Protos/iam/account/v1/(or the appropriate path), then reference it in the.csprojfile so it generates on build.<ItemGroup>
<Protobuf Include="Protos\iam\account\v1\account.proto" GrpcServices="Server" />
</ItemGroup> -
Implement the service in
Services/. Extend the generated...ServiceBaseand overrideOnMessage.// Services/UserLoggedOutService.cs
public class UserLoggedOutService
: UserAuthenticationUserLoggedOutService.UserAuthenticationUserLoggedOutServiceBase
{
private readonly ILogger<UserLoggedOutService> _Logger;
public UserLoggedOutService(ILogger<UserLoggedOutService> logger,
IAccelByteServiceProvider abProvider)
{
_Logger = logger;
}
public override Task<Empty> OnMessage(UserLoggedOut request, ServerCallContext context)
{
_Logger.LogInformation("Received UserLoggedOut event: {@Request}", request);
// ... your custom logic ...
return Task.FromResult(new Empty());
}
} -
Register the service in
Program.cswith anotherMapGrpcService<T>()call.app.MapGrpcService<UserLoggedInService>();
app.MapGrpcService<UserThirdPartyLoggedInService>();
app.MapGrpcService<UserLoggedOutService>(); // <-- your new handler
Each additional event is one new service class plus one more MapGrpcService<T>() call.
Reference: extend-event-handler-go
-
Add the proto and regenerate. Drop the event spec proto into
pkg/proto/accelbyte-asyncapi/, then run the following command.make proto # runs proto.sh -> protoc, generating into pkg/pb/ -
Implement the service. Create a handler in
pkg/service/. Embed the generatedUnimplemented...ServiceServerand implementOnMessage.// pkg/service/logoutHandler.go
type LogoutHandler struct {
pb.UnimplementedUserAuthenticationUserLoggedOutServiceServer
namespace string
}
func (o *LogoutHandler) OnMessage(ctx context.Context, msg *pb.UserLoggedOut) (*emptypb.Empty, error) {
scope := common.GetScopeFromContext(ctx, "LogoutHandler.OnMessage")
defer scope.Finish()
scope.Log.Info("received an event", "event", msg)
// ... your custom logic ...
return &emptypb.Empty{}, nil
} -
Register the service in
main.go, alongside the existing ones.// Existing handlers
loginHandler := service.NewLoginHandler(configRepo, tokenRepo, namespace)
pb.RegisterUserAuthenticationUserLoggedInServiceServer(s, loginHandler)
thirdPartyLoginHandler := service.NewThirdPartyLoginHandler(configRepo, tokenRepo, namespace)
pb.RegisterUserAuthenticationUserThirdPartyLoggedInServiceServer(s, thirdPartyLoginHandler)
// Your new handler — just add another Register call
logoutHandler := &service.LogoutHandler{ /* ... */ }
pb.RegisterUserAuthenticationUserLoggedOutServiceServer(s, logoutHandler)
Each additional event is one new handler file plus one more pb.Register...Server(s, handler) call.
Reference: extend-event-handler-java
The project uses the grpc-spring-boot-starter plugin, so proto generation and service registration are automatic.
-
Add the proto. Place the event spec proto under
src/main/proto/accelbyte-asyncapi/. The Gradle build (./gradlew build) generates the code automatically. -
Implement and auto-register the service. Extend the generated
...ServiceImplBaseand annotate the class with@GRpcService. The annotation registers it with the gRPC server on startup, so no manual wiring is needed.// src/main/java/net/accelbyte/service/LogoutHandler.java
@Slf4j
@GRpcService // <-- auto-registers this service with the gRPC server
public class LogoutHandler
extends UserAuthenticationUserLoggedOutServiceGrpc.UserAuthenticationUserLoggedOutServiceImplBase {
@Override
public void onMessage(UserLoggedOut request, StreamObserver<Empty> responseObserver) {
log.info("received a message: {}", request);
// ... your custom logic ...
responseObserver.onNext(Empty.getDefaultInstance());
responseObserver.onCompleted();
}
}
To handle more events, add one @GRpcService class per event. Spring discovers and registers each one automatically.
Reference: extend-event-handler-python
-
Add the proto and regenerate. Place the proto under
proto/, then run the following command.make proto # runs proto.sh -> protoc, generating *_pb2.py / *_pb2_grpc.py into src/ -
Implement the servicer in
src/app/services/. Subclass the generated...Servicerand implement the asyncOnMessagemethod.# src/app/services/logout_handler.py
class AsyncLogoutHandlerService(UserAuthenticationUserLoggedOutServiceServicer):
full_name: str = DESCRIPTOR.services_by_name[
"UserAuthenticationUserLoggedOutService"
].full_name
def __init__(self, namespace: str, sdk=None, logger=None):
self.namespace = namespace
self.sdk = sdk
self.logger = logger
async def OnMessage(self, request: UserLoggedOut, context):
# ... your custom logic ...
return Empty() -
Register the servicer in
src/app/__main__.pyby appending anotherAppGRPCServiceOpt.from account_pb2_grpc import (
add_UserAuthenticationUserLoggedInServiceServicer_to_server,
add_UserAuthenticationUserLoggedOutServiceServicer_to_server,
)
# Existing handler
opts.append(
AppGRPCServiceOpt(
AsyncLoginHandlerService(namespace=namespace, sdk=sdk, logger=logger),
AsyncLoginHandlerService.full_name,
add_UserAuthenticationUserLoggedInServiceServicer_to_server,
)
)
# Your new handler — just append another opt
opts.append(
AppGRPCServiceOpt(
AsyncLogoutHandlerService(namespace=namespace, sdk=sdk, logger=logger),
AsyncLogoutHandlerService.full_name,
add_UserAuthenticationUserLoggedOutServiceServicer_to_server,
)
)
app = App(port=port, env=env, logger=logger, opts=opts)
await app.run()
Each additional event is one new servicer class plus one more opts.append(...) call.
Test multiple events locally
All reference apps expose gRPC reflection and run on port 6565 by default, so you can test each handler with a gRPC client such as Postman.
- Connect to
localhost:6565with plaintext and reflection enabled. - From the method dropdown, pick the service you want to test, for example
UserAuthenticationUserLoggedInService/OnMessageorUserAuthenticationUserLoggedOutService/OnMessage. - Send a sample event payload as JSON, including fields such as
namespaceanduserId. - Confirm that a successful call returns an empty response (
google.protobuf.Empty).
Repeat for each event to confirm that every registered handler responds. Once deployed, AGS routes real events to the matching service automatically.