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
go get github.com/atulsinghhhh/swalvo-sdks/sdks/go/livqenoimport "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.
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)
}| Option | Default | Notes |
|---|---|---|
WithBaseURL(url) | http://localhost:4100 | Required when NODE_ENV or LIVQENO_ENV is production. |
WithTimeout(d) | 10s | Per attempt. |
WithMaxRetries(n) | 2 | Retries 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.
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 deleteA 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, terminalclient.Liveis the same pointer asclient.LiveStreams.- The method you call decides the role:
AddHostmakes a host,CreateViewerTokenmakes a viewer. Nothing in the request can change that. - For a
BROADCASTstream, pass&livqeno.ViewerTokenOptions{AcceptPlayback: true}and readviewer.Playback.LLHLSURL(orviewer.Playback.URL) instead of getting a 409. See Viewers. client.LiveStreams.Stage,.Restreamsand.Ingresscover the stage, restreaming and OBS ingest.LiveStreamsResourcealso hasListandGetto look up streams,Updateto edit one, andLeavefor 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.
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 healthThese 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
| Setting | Behavior |
|---|---|
| Retries | Network 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. |
| Timeouts | WithTimeout, default 10 s, per attempt. Your context's deadline also applies. |
| Lists | Chat 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
- Build a token endpoint and a working call: Quickstart (choose the Go tab).
- See everything a token can allow: Access tokens.
- Set up the app side: Next.js, React, React Native, or Flutter.