livqenoDocs

SDKsGo

Go SDK

Make tokens and manage rooms, chat and streams from your Go backend. Standard library only.

The Go SDK is for your Go backend (net/http, Gin, Echo, chi, or a plain worker). It holds your secret API key and talks to the Livqeno REST API for you.

Its main job is making tokens. Your app (web, React, React Native, Flutter, iOS or Android) can't join a call or chat without one, and only a backend holding the API key can make one. In a typical app, this SDK sits behind an endpoint like POST /join-room: your app calls it, your code checks who the user is, and this SDK returns a token for that user.

It mirrors the TypeScript SDK resource for resource: the same endpoints, the same wire fields, and the same retry and error rules, with Go names (client.LiveStreams.CreateViewerToken). It uses only the standard library.

You need: Go 1.22 or later, a project, and an API key. See API credentials.

Install

Terminal
go get github.com/atulsinghhhh/swalvo-sdks/sdks/go/livqeno
import "github.com/atulsinghhhh/swalvo-sdks/sdks/go/livqeno"

Set up the client

On your backend, create one client at startup and share it. A Client is safe for concurrent use.

main.go
client := livqeno.New(
	os.Getenv("LIVQENO_API_KEY"),                      // rvk_...; never hardcode it
	livqeno.WithBaseURL(os.Getenv("LIVQENO_API_URL")), // https://api.livqeno.com
)

Pass the API key yourself. The SDK never reads it from the environment on its own.

New never fails, so it fits on one line. A configuration problem (an empty API key, no base URL in production, a bad option) comes back from client.Err() and from every call, as a *livqeno.Error with code RAVEN_INVALID_CONFIG. To fail fast at startup, use NewClient:

client, err := livqeno.NewClient(apiKey, livqeno.WithBaseURL(baseURL))
if err != nil {
	log.Fatal(err)
}
OptionDefaultNotes
WithBaseURL(url)http://localhost:4100Required when NODE_ENV or LIVQENO_ENV is production.
WithTimeout(d)10sPer attempt.
WithMaxRetries(n)2Retries network errors, timeouts and 429/502/503/504 only.
WithHTTPClient(hc)&http.Client{}For a custom transport or proxy.

Every method takes a context.Context. Cancelling it, or letting its deadline pass, stops the call at once, and it is never retried.

Optional request fields are pointers or omitempty. Fill pointers in place with livqeno.String, livqeno.Bool, livqeno.Int and livqeno.Float64. A response field that can be JSON null is a pointer, so it is nil for null.

RTC tokens

Call this when a user is about to join a call, once per user, per join. Take Identity from your own login session, never from the request body, or any user could join as anyone.

join.go
http.HandleFunc("POST /join-room", func(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := currentUser(r) // your own session lookup
 
	// Every participant calls this. GetOrCreate returns the existing room
	// to the second caller instead of a 409.
	room, err := client.Rooms.Create(ctx, livqeno.CreateRoomParams{Name: "demo-room", GetOrCreate: true})
	if err != nil {
		http.Error(w, "could not create room", http.StatusBadGateway)
		return
	}
 
	grant, err := client.Tokens.Create(ctx, livqeno.CreateTokenParams{
		Room:     room.ID,
		Identity: user.ID, // from your session, never the body
		Permissions: &livqeno.TokenPermissions{
			Join: livqeno.Bool(true), Subscribe: livqeno.Bool(true), Publish: livqeno.Bool(true),
		},
		ExpiresIn: 600, // seconds, 30 to 21600; 0 uses the API default
	})
	if err != nil {
		http.Error(w, "could not mint token", http.StatusBadGateway)
		return
	}
 
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(grant) // forward the whole object
})

IssuedToken carries the API's own JSON tags, so encoding it gives your app the same object the TypeScript SDK returns: token, endpoint, iceServers, telemetryUrl, expiresAt and the rest. Return it whole. The iceServers part is what lets calls work on mobile networks.

client.Rooms also has List, Get and Delete. To moderate a room while it's running — kick someone out, force-mute their mic, or change what they're allowed to do — use client.Rooms.Participants: List, Get, Remove, Mute, and UpdatePermissions. See Rooms & Participants.

Chat

Your backend creates conversations, decides who is a member, and mints a chat token for each user. The browser never creates conversations or adds members itself.

conv, err := client.Chat.CreateConversation(ctx, livqeno.CreateConversationParams{
	Name: "support-room-42", Type: livqeno.ConversationChannel, GetOrCreate: true,
	Members: []livqeno.ChatMemberInput{
		{UserID: "alice", Role: livqeno.ChatRoleAdmin},
		{UserID: "bob"},
	},
})
 
chatTok, err := client.Chat.CreateToken(ctx, livqeno.CreateChatTokenParams{
	UserID:        user.ID,
	Conversations: []string{conv.PublicID},
	Scopes:        []livqeno.ChatScope{livqeno.ChatScopeRead, livqeno.ChatScopeSend},
	ExpiresIn:     900,
})
// Hand chatTok.Token to the browser. Nothing else.

Members, messages and moderation:

member, err := client.Chat.AddMember(ctx, "support-room-42", livqeno.AddMemberParams{UserID: "carol"})
err = client.Chat.RemoveMember(ctx, "support-room-42", "carol")
 
msg, err := client.Chat.SendMessage(ctx, "support-room-42", livqeno.SendChatMessageParams{
	SenderID: "system", Type: livqeno.MessageSystem, Text: livqeno.String("Maintenance in 5 minutes"),
	ClientMessageID: "maint-1", // idempotency key
})
 
page, err := client.Chat.ListMessages(ctx, "support-room-42", &livqeno.ListChatMessagesParams{Limit: 50})
deleted, err := client.Chat.DeleteMessage(ctx, msg.ID) // soft delete

A conversation created with no members can't be used by any chat token. List the members when you create it, or call AddMember before you mint their tokens.

client.Chat.ListConversations, .GetConversation and .ListMembers let you look up what you've already created — useful for building an admin view.

Live streaming

A live stream is one main host, optional co-hosts, and many viewers who only watch. On your backend: create the stream, start it, then give each person the right credential.

stream, err := client.LiveStreams.Create(ctx, livqeno.CreateLiveStreamParams{
	Title: "Friday Q&A", HostIdentity: "user-123",
})
stream, err = client.LiveStreams.Start(ctx, stream.ID) // CREATED -> LIVE
 
// Registering a co-host mints full-publish RTC + moderator chat
// credentials in one call. Hand the result to the client SDK unchanged.
host, err := client.LiveStreams.AddHost(ctx, stream.ID, livqeno.AddHostParams{Identity: "user-456", Role: livqeno.RoleCoHost})
 
// A viewer credential is always subscribe-only.
viewer, err := client.LiveStreams.CreateViewerToken(ctx, stream.ID, "user-789", nil)
 
err = client.LiveStreams.RemoveHost(ctx, stream.ID, "user-456")
stream, err = client.LiveStreams.End(ctx, stream.ID) // LIVE -> ENDED, terminal
  • client.Live is the same pointer as client.LiveStreams.
  • The method you call decides the role: AddHost makes a host, CreateViewerToken makes a viewer. Nothing in the request can change that.
  • For a BROADCAST stream, pass &livqeno.ViewerTokenOptions{AcceptPlayback: true} and read viewer.Playback.LLHLSURL (or viewer.Playback.URL) instead of getting a 409. See Viewers.
  • client.LiveStreams.Stage, .Restreams and .Ingress cover the stage, restreaming and OBS ingest.
  • LiveStreamsResource also has List and Get to look up streams, Update to edit one, and Leave for when a participant wants to leave on their own. See Streams.

Webhooks

Livqeno signs every delivery with swalvo-signature: t=<unix>,v1=<hmac>. VerifyWebhookRequest reads the body, checks the signature in constant time, rejects a timestamp more than 300 s from now, and decodes the event envelope. It puts the body back on the request afterwards.

webhooks.go
http.HandleFunc("POST /webhooks/livqeno", func(w http.ResponseWriter, r *http.Request) {
	event, err := livqeno.VerifyWebhookRequest(r, os.Getenv("LIVQENO_WEBHOOK_SECRET"))
	if err != nil {
		http.Error(w, "invalid signature", http.StatusBadRequest)
		return
	}
	// Deliveries can repeat: deduplicate on event.ID.
	switch event.Type {
	case "live_stream.started":
		// json.Unmarshal(event.Data, &yourStruct)
	}
	w.WriteHeader(http.StatusNoContent)
})

If you already have the raw body, verify those exact bytes. Re-encoded JSON won't match.

err := livqeno.VerifyWebhookSignature(rawBody, r.Header.Get(livqeno.WebhookSignatureHeader), secret)
// errors.Is(err, livqeno.ErrWebhookSignatureMismatch), ErrWebhookTimestampOutOfRange,
// ErrWebhookInvalidHeader, ErrWebhookMissingSecret
 
// To test your own handler, sign a payload the way Livqeno does:
header := livqeno.SignWebhookPayload(body, secret, time.Now())

More: Webhooks.

Errors

Every failure is a *livqeno.Error. Branch on Code, not on the message, and log RequestID: support can use it to find the exact request.

_, err := client.Rooms.Get(ctx, "2f1b6c3e-8d4a-4b7e-9c21-5a0e7d3f9b10")
var lerr *livqeno.Error
if errors.As(err, &lerr) {
	log.Println(lerr.Code)       // e.g. "RAVEN_ROOM_NOT_FOUND"
	log.Println(lerr.StatusCode) // e.g. 404 (0 if no response arrived)
	log.Println(lerr.RequestID)  // matches the API's x-request-id
}
// or: if lerr, ok := livqeno.AsError(err); ok { ... }

The API's own code is passed through. If a proxy strips the JSON body, the code is derived from the status (RAVEN_AUTH_ERROR, RAVEN_NOT_FOUND, RAVEN_RATE_LIMITED, RAVEN_INTERNAL_ERROR, ...). The SDK adds RAVEN_INVALID_CONFIG, RAVEN_TIMEOUT, RAVEN_NETWORK_ERROR and RAVEN_UNKNOWN_ERROR (a 2xx body it couldn't decode). For a context deadline, errors.Is(err, context.DeadlineExceeded) also works. All codes: Error Codes.

Account and observability

project, err := client.Projects.Get(ctx)
conns, err := client.Connections.List(ctx, nil)       // active RTC/chat sockets
conn, err := client.Connections.Get(ctx, connID)
errs, err := client.Errors.List(ctx, nil)             // logged API/SFU errors
metrics, err := client.Metrics.Get(ctx, livqeno.MetricsRange{})
diag, err := client.Diagnostics.Get(ctx)              // dependency health

These calls aren't about any single room or stream — they look at your whole project: its live connections, past errors, usage metrics, and the health of Livqeno's own dependencies. You can see the same data in the dashboard, or through the Observability API.

Retries, timeouts, and lists

SettingBehavior
RetriesNetwork errors, timeouts, 429, 502, 503 and 504 only, with exponential backoff (300 ms, 600 ms, 1.2 s). Up to WithMaxRetries, default 2. Other 4xx errors are never retried.
TimeoutsWithTimeout, default 10 s, per attempt. Your context's deadline also applies.
ListsChat history uses cursors (page.NextCursor). Every other list returns a plain slice.

Security

The client never prints its API key: String, %+v, %#v and json.Marshal show only the base URL. Error strings never contain the key, and if a server echoes it back, the SDK redacts it from Message and Details. See Authentication.

Next steps

  1. Build a token endpoint and a working call: Quickstart (choose the Go tab).
  2. See everything a token can allow: Access tokens.
  3. Set up the app side: Next.js, React, React Native, or Flutter.