Skip to content

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
  1. 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_url and pay. When the payment lands your tenant becomes active. Until then joins are refused with 402 Payment Required.

  2. 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-go
    package main
    import (
    "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 authentication
    var 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 + video
    Preset: 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.

  3. Add MoqomKit to your app.

    In Xcode, File → Add Package Dependencies… and enter https://github.com/moqom-cloud/moqom-swift, or in Package.swift:

    .package(url: "https://github.com/moqom-cloud/moqom-swift", from: "0.1.0")

    Add NSCameraUsageDescription and NSMicrophoneUsageDescription to Info.plist, and the Keychain entitlement your app already uses for credentials.

  4. Join and go live.

    import MoqomKit
    import MoqomCore
    func 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.

  • 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 renew closure 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.events and in room.roster.