Za-go wraps Zalo web/mobile behaviour behind a Go API. The useful parts are session state, service grouping, event channels, QR login helpers, and the risks of unofficial APIs.

Za-go: what an unofficial messaging client looks like in Go


Za-go is an unofficial Go client for Zalo. Unofficial messaging clients sit in an awkward place: useful for automation experiments, but fragile because they depend on private API behaviour that can change without notice. They can also violate platform terms if used carelessly.

Read this kind of project as a Go architecture study, not as an endorsement of automating a consumer messaging platform without permission.

The public API is one facade

The main type is ZaloAPI:

type ZaloAPI struct {
	state *app.State
	hub   *worker.EventHub
	auth  *authcore.LoginAuth
	// grouped services: send, group, socket, properties, get...
}

The constructor wires a shared state object and an event hub into a set of service packages:

func Zalo(phone, password, imei string, sessionCookies any, userAgent string, autoLogin bool, login int) (*ZaloAPI, error) {
	state := app.NewState(...)
	hub := worker.NewEventHub()
	getSvc := getapi.NewGetAPI(state, login, hub)
	return &ZaloAPI{
		state: state,
		hub: hub,
		auth: authcore.NewLoginAuth(state),
		send: handle.NewSendAPI(state, login, hub),
		group: groupapi.NewGroupAPI(state, login, hub, getSvc),
	}, nil
}

That is a common shape for API clients with many endpoints. Keep the user-facing type small, then group endpoint families behind internal services that share state.

Session state is the hard part

Messaging clients are mostly session management. Za-go exposes SetSession, Login, IsLoggedIn, profile helpers, and QR-code authentication methods. The public methods mostly delegate to the state/auth/socket packages.

func (z *ZaloAPI) SetSession(sessionCookies any) bool
func (z *ZaloAPI) Login(phone, password, imei, userAgent string) error
func (z *ZaloAPI) IsLoggedIn() bool

That separation is useful. Login code changes more often than message-sending code when a private platform changes its web flow.

Events are exposed as channels

The library exposes receive-only channels for socket events:

func (z *ZaloAPI) MessageEvents() <-chan worker.MessageEvent
func (z *ZaloAPI) GroupEvents() <-chan worker.GroupEventEnvelope
func (z *ZaloAPI) DeliveryEvents() <-chan worker.DeliveryEvent
func (z *ZaloAPI) SocketErrors() <-chan worker.SocketErrorEvent

That is idiomatic Go for a realtime client. The socket layer owns reads and reconnect behaviour. Consumers range over channels or select on the events they care about.

A channel API also makes backpressure visible. If callers stop reading events, the event hub has to decide whether to block, buffer, or drop. That is an important design decision for any messaging client.

The facade has too many methods, but the grouping helps

ZaloAPI exposes many methods: sending text, voice, video, files, reactions, stickers, local images, friend actions, group actions, QR helpers, upload helpers, and profile fetches.

That can get noisy, but it gives users one import and one client value. The internal services keep implementation grouped even if the public facade is broad.

For a production SDK, I would consider exposing grouped sub-clients directly:

z.Messages.Send(...)
z.Groups.AddMember(...)
z.Auth.Login(...)

The current facade is simpler for quick scripts, which is probably the target audience.

What to take from Za-go

The useful Go lessons are the shared state object, grouped endpoint services, receive-only event channels, and facade API. The operational lesson is caution: unofficial clients are brittle, and automation against private messaging APIs should be limited to accounts and use cases you are allowed to control.

If you build official API clients, the same package structure applies. If you build unofficial ones, expect maintenance to be the main cost.