5-minute quickstart
This walk-through takes you from nothing to two devices in a room:
sequenceDiagram
autonumber
participant Dev as You
participant Cloud as moqom.cloud
participant BE as Your backend
participant App as iOS app (MoqomKit)
participant Relay as MOQOM relay
Dev->>Cloud: Sign up, pay first top-up
Cloud-->>Dev: Backend API key (mqk_…), shown once
App->>BE: "I'm alice, here's my device thumbprint, room demo"
BE->>Cloud: MintClientToken (gRPC, Bearer mqk_…)
Cloud-->>BE: token (≤ 15 min)
BE-->>App: token + expiry
App->>Relay: connect with token, join room, publish
-
Sign up and fund your tenant.
Create an account at moqom.cloud. You receive a tenant ID, your first backend API key, and a checkout link for your first top-up. The key is shown once — store it in your secret manager now.
Prefer the API? The same thing over REST:
Terminal window curl -sS https://api.moqom.cloud/v1/signup \-H 'Content-Type: application/json' \-d '{"email":"dev@example.com","name":"Example Inc"}'{"tenant": "t_3f9a0c1d2e4b5a69","status": "pending","api_key": "mqk_9c1e2f3a4b5d6e7f_x2Vt…","key_id": "9c1e2f3a4b5d6e7f","checkout_url": "https://checkout.stripe.com/c/pay/cs_…","note": "The API key is shown once. Store it now; it cannot be recovered, only revoked and replaced."}Open
checkout_urland pay. When the payment lands your tenant becomesactive. Until then joins are refused with 402 Payment Required. -
Mint tokens from your backend.
Your backend already knows who is signed in. Add one route that asks MOQOM for a token for that user. Here it is in Go with
moqom-go:Terminal window go get github.com/moqom-cloud/moqom-gopackage mainimport ("encoding/json""net/http""os"moqom "github.com/moqom-cloud/moqom-go")func main() {client, err := moqom.Dial("api.moqom.cloud:443",moqom.WithCredential(os.Getenv("MOQOM_API_KEY")),moqom.WithTenant(os.Getenv("MOQOM_TENANT")))if err != nil {panic(err)}defer client.Close()http.HandleFunc("POST /moqom-token", func(w http.ResponseWriter, r *http.Request) {user := currentUser(r) // your own authenticationvar body struct{ Room, Thumbprint string }if err := json.NewDecoder(r.Body).Decode(&body); err != nil {http.Error(w, "bad request", http.StatusBadRequest)return}token, err := client.MintToken(r.Context(), moqom.TokenRequest{Username: user.Name,DeviceThumbprint: body.Thumbprint,Room: body.Room,Publish: moqom.PublishCamera, // audio + videoPreset: moqom.PresetNone,})if err != nil {http.Error(w, err.Error(), http.StatusBadGateway)return}json.NewEncoder(w).Encode(map[string]any{"token": token.Token,"expiresAt": token.ExpiresAt,"username": token.Identity.Username,})})http.ListenAndServe(":8080", nil)}The room must exist before a token can name it. Create it once with
client.CreateRoom(ctx, moqom.NewRoom{Name: "demo"}), or from the console. -
Add MoqomKit to your app.
In Xcode, File → Add Package Dependencies… and enter
https://github.com/moqom-cloud/moqom-swift, or inPackage.swift:.package(url: "https://github.com/moqom-cloud/moqom-swift", from: "0.1.0")Add
NSCameraUsageDescriptionandNSMicrophoneUsageDescriptiontoInfo.plist, and the Keychain entitlement your app already uses for credentials. -
Join and go live.
import MoqomKitimport MoqomCorefunc joinDemo(as name: String) async throws {// A key generated on this device (Secure Enclave where available).let deviceKey = try DeviceKey.installed()// Ask *your* backend for a token (step 2). `fetchToken` is your code: it POSTs to// /moqom-token and wraps the JSON reply in MoqomKit's `IssuedToken`.let fetchToken: @Sendable () async throws -> IssuedToken = {try await myBackend.moqomToken(room: "demo", thumbprint: deviceKey.thumbprint)}let issued = try await fetchToken()let identity = ParticipantIdentity(username: try Username(issued.username),device: try DeviceID(thumbprint: deviceKey.thumbprint))let session = MoqomSession(transport: MoqtTransport.network(),credentials: DeviceCredentials(identity: identity,token: issued.token,deviceKey: deviceKey,expiresAt: issued.expiresAt,renew: fetchToken // called before expiry; the call continues uninterrupted),tenant: TenantID("t_3f9a0c1d2e4b5a69"))try await session.connect(to: RelayEndpoint(host: "relay.example.moqom.cloud"))let room = try await session.join(room: RoomID("demo"))// Publishing is always an explicit act.let camera = try CameraSource(format: CaptureFormat(size: PixelSize(width: 1280, height: 720), frameRate: 30))camera.start()try await room.startBroadcast(from: camera).start()let microphone = try MicrophoneSource()try microphone.start()try await room.startAudio(from: microphone).start()for await event in room.events {print(event) // participantJoined, muteChanged, grantsChanged, …}}Run it on two devices with two usernames and they will see and hear each other.
What just happened
Section titled “What just happened”- Your API key authenticated your backend to the control plane. It never left your server.
- Your backend minted a client token naming one user, one device, one room, and the right
to publish audio and video. It expires in at most 15 minutes; the
renewclosure lets the SDK fetch the next one without interrupting the call. - The app connected to a relay over QUIC, joined the room, and published. Every other
participant’s arrival shows up in
room.eventsand inroom.roster.
- iOS & macOS guide — room state, mutes, stage invitations, moderation callbacks.
- Moderation guide — kicks, bans and mutes from your backend.
- Pricing model — how usage is charged against your credit.