Go Server API
The full reference for sdk-go-server covers NewSigner, the claims surface, and the connection to Celeris from a Go backend.
go get github.com/useceleris/sdk-go-server
The module requires Go 1.27 or newer, and it runs on trusted servers only. The package name is celerisserver. This module holds your signing secret and must never ship in a browser, mobile or desktop application. It depends on sdk-go-client for the credential types and errors, and it signs with the standard library.
NewSigner()
func NewSigner(options SignerOptions) (*Signer, error)
type SignerOptions struct {
ClientID string // no colon, CR or LF
SigningSecret string
Clock func() time.Time // nil means time.Now; a test seam
}
func (signer *Signer) Sign(claims Claims) (celeris.Credentials, error)
Sign is deterministic for fixed inputs. It validates the claims, stamps the current time and returns celeris.Credentials. A Signer is safe for concurrent use and holds no resources. It keeps the secret only inside the digest it computes, so the secret never appears when you print the signer. SignerOptions implement String, Format and LogValue, so they print as redacted through fmt and slog. fmt prints a struct's unexported fields field by field, so pass the options to NewSigner and do not keep them in a struct of your own.
signer, err := celerisserver.NewSigner(celerisserver.SignerOptions{
ClientID: os.Getenv("CELERIS_CLIENT_ID"),
SigningSecret: os.Getenv("CELERIS_SIGNING_SECRET"),
})
if err != nil {
log.Fatal(err)
}
credentials, err := signer.Sign(celerisserver.Claims{
Channels: celerisserver.RestrictedChannels("room-42"),
Permissions: celerisserver.RestrictedSegments(
celerisserver.SegmentClaim{SegmentID: "chat", Read: true, Write: true},
),
Reference: "user-8317",
Replay: celerisserver.ReplayLookback(30 * time.Second),
})
if err != nil {
log.Fatal(err)
}
Return credentials to the application unchanged. It encodes as {"payload": ..., "signature": ...}. Both values are opaque: never parse, re-encode or cache them.
The claims surface
type Claims struct {
Channels ChannelScope
Permissions SegmentPermissions
Reference string
Replay Replay
AllowEcho bool
}
type SegmentClaim struct {
SegmentID string
Read bool
Write bool
}
func AllChannels() ChannelScope
func RestrictedChannels(references ...string) ChannelScope
func AllSegments(read, write bool) SegmentPermissions
func RestrictedSegments(segments ...SegmentClaim) SegmentPermissions
func ReplayBacklog() Replay
func ReplayLookback(lookback time.Duration) Replay
| Field | Values | Notes |
|---|---|---|
Channels | AllChannels() or RestrictedChannels(references...) | Required. References are 1 to 255 characters of [A-Za-z0-9_-], unique, at least one. None is refused, never read as "all". |
Permissions | AllSegments(read, write) or RestrictedSegments(claims...) | Required. A restricted list can be empty, which grants nothing. Segment ids are unique, non-empty, no CR or LF. |
Reference | string | Identity label in message metadata and presence. No colon, CR or LF. Leave it empty to let the server generate one. |
Replay | ReplayBacklog() or ReplayLookback(d) | The zero value replays nothing. A lookback is whole milliseconds, 0 to 4294967295 ms. |
AllowEcho | bool | Defaults to false: a connection does not receive its own publishes. |
ChannelScope, SegmentPermissions and Replay are opaque: make them only with these functions. The zero Claims is invalid, and every string must be valid UTF-8. Unrestricted access is always an explicit opt-in:
credentials, err := signer.Sign(celerisserver.Claims{
Channels: celerisserver.AllChannels(),
Permissions: celerisserver.AllSegments(true, false),
})
Write: true but Read: false can publish and cannot subscribe. The server refuses its subscription with a PermissionDeniedError. On default, such a token receives nothing, with no error. If a client connects but never receives messages, check Read first.NewCredentialProvider()
Sometimes a backend is itself a realtime client, for example a worker that publishes events. This function bridges the signer to the client's provider.
func NewCredentialProvider(signer *Signer, claims ClaimsFunc) (celeris.CredentialProvider, error)
type ClaimsFunc func(ctx context.Context, request celeris.CredentialRequest) (Claims, error)
| Argument | Required | Meaning |
|---|---|---|
signer | Yes | The signer from NewSigner |
claims | Yes | Server-owned authorization function, called with each attempt's context |
The provider calls claims(ctx, request) on every attempt, then signs with a fresh timestamp. If the context is already done before claims runs, or after it returns, the provider fails and does not sign. See the client types for CredentialRequest's fields.
provider, err := celerisserver.NewCredentialProvider(signer, func(_ context.Context, request celeris.CredentialRequest) (celerisserver.Claims, error) {
claims := celerisserver.Claims{
Channels: celerisserver.RestrictedChannels(request.ChannelReference),
Permissions: celerisserver.RestrictedSegments(
celerisserver.SegmentClaim{SegmentID: "chat", Read: true, Write: true},
),
Reference: "worker-1",
}
if request.Reconnect {
claims.Replay = celerisserver.ReplayLookback(min(request.ReplayLookback, 30*time.Second))
}
return claims, nil
})
if err != nil {
log.Fatal(err)
}
client, err := celeris.NewClient(celeris.ClientOptions{CredentialProvider: provider})
if err != nil {
log.Fatal(err)
}
channel, err := client.Channel("room-42")
if err != nil {
log.Fatal(err)
}
defer channel.Close()
if err := channel.Connect(ctx); err != nil {
log.Fatal(err)
}
Your claims function runs freshly on every attempt and is the sole authority over scope. The provider copies nothing from the request into the claims automatically. Trusted worker code chooses this example's channel. In an HTTP credential endpoint, authorize the requested channel against the verified user's access before you sign it. The 30-second replay cap is an example policy, not a retention promise.
Invalid claims from the provider reach the client as its generic ErrTransport, which never repeats what went wrong. To check a claims function, sign its result directly with signer.Sign, which returns an ErrConfiguration that names the claim and the rule.
A fresh signature does not guarantee a particular credential lifetime, and it does not revoke an existing connection. Keep signed credentials confidential and pass them unchanged.
Errors
Invalid options and claims return a *celeris.Error with Code celeris.ErrConfiguration, so errors.Is(err, celeris.ErrConfiguration) matches them. The message names each field that failed and the rule it broke, such as Invalid claims. Channels.References: Must not repeat a channel reference.. The message never repeats your input, secrets or claims.
| Returned by | When |
|---|---|
NewSigner | ClientID is empty or contains a colon, CR or LF, SigningSecret is empty, or either value is invalid UTF-8 |
Signer.Sign | The claims are invalid, or Clock returned a time outside 1 to 253402300799999 Unix milliseconds |
NewCredentialProvider | signer or claims is nil |
An error returned by your own claims function propagates unchanged from the provider. Through Connect it appears as ErrTransport, like any provider failure.