Cariosan

Users

How Cariosan represents end users and issues JWTs for client SDKs.

3 min readUpdated Mar 14, 2026

A user is the identity an end customer sees. Cariosan doesn't authenticate users itself — your app already does that. Instead, you tell Cariosan about your users and it mints short-lived JWTs the client SDK can use to authenticate WebSocket connections and REST reads.

The external_id

Every user has two identifiers:

  • An internal UUID, assigned by Cariosan, used as the foreign key target for memberships and messages.
  • An external_id you supply ("user_alice", "auth0|123", etc.). This is the stable handle you use in every SDK call.

Tip

Pick an external_id that won't change if the user renames themselves. The account row's primary key in your own database is a natural choice.

Upsert semantics

There is no "create" endpoint — just upsert. Call UpsertUser every time the user's name or avatar changes in your app:

users.go
client.UpsertUser(ctx, cariosan.UpsertUserRequest{
    ExternalID: "user_alice",
    Name:       "Alice Setiawan",
    AvatarURL:  "https://cdn.example.com/users/alice.jpg",
    Metadata:   map[string]any{"plan": "pro"},
})
  • First call → row inserted, user.created webhook fires.
  • Subsequent calls → row updated in place, no webhook.

Issuing a client JWT

Your backend mints a per-user JWT and returns it to the browser (or mobile client). The client SDK stores it in memory — never persist it to localStorage if you have alternatives, since anything in localStorage is exposed to XSS.

token.go
token, _ := client.IssueUserToken(ctx, "user_alice", 10*time.Minute)
// Return token.Token to the browser.

Token lifetime

Recommended: short tokens (about 10 minutes) and a token provider on the client. The SDK calls your token endpoint for the first token and again shortly before each one expires, moving the open WebSocket to the new token without reconnecting. A stolen token is then only useful for minutes, and nothing needs to be stored on the device.

  • The default TTL, when you pass none, follows CARIOS_JWT_TTL_HOURS (24 h). The maximum is 30 days.
  • A fixed long-lived token (a plain string) still works, but the SDK can only use it until the server rejects it. After that your app must hand it a new one with client.setToken().
  • To cut a user off before their tokens expire, revoke them with POST /v1/users/{external_id}/revoke-tokens (Go: RevokeUserTokens). Open connections stop within about 5 seconds. A token provider then asks your backend for a new token, so your token endpoint decides whether the user still gets one.

Deleting a user

DeleteUser hard-deletes the user row and cascades to their channel memberships.

Hard delete

Deleting a user does not delete their messages. Those stay attributed to the deleted user's UUID — this is intentional: removing message history leaves confusing gaps in conversations other people still see. Prefer soft delete (flip a deleted_at column in your own DB) when history matters; once Cariosan's row is gone, the user can't be re-linked to past messages.

Was this page helpful?

On this page