Skip to content

Daily.co Integration ​

Daily.co provides videocall room management for appointments.

Features ​

  • Creates private rooms with max 2 participants
  • Room name format: restartix-{orgID}-{appointmentID}
  • Features: screenshare on, chat on, no prejoin UI
  • Auto-eject at expiration time
  • Create/update/delete room lifecycle

Go Client ​

go
// internal/integration/dailyco/client.go

type Client struct {
    httpClient *http.Client
    baseURL    string
    apiKey     string
}

func NewClient(apiKey, baseURL string) *Client {
    return &Client{
        httpClient: &http.Client{Timeout: 10 * time.Second},
        baseURL:    baseURL,
        apiKey:     apiKey,
    }
}

func (c *Client) CreateRoom(ctx context.Context, orgID, appointmentID int64, expiresAt time.Time) (*Room, error) {
    body := CreateRoomRequest{
        Name:    fmt.Sprintf("restartix-%d-%d", orgID, appointmentID),
        Privacy: "private",
        Properties: RoomProperties{
            MaxParticipants:   2,
            EnableScreenshare: true,
            EnableChat:        true,
            EnablePrejoinUI:   false,
            EnableNetworkUI:   false,
            EjectAtRoomExp:    true,
            Exp:               expiresAt.Unix(),
        },
    }

    resp, err := c.doRequest(ctx, http.MethodPost, "/rooms", body)
    if err != nil {
        return nil, fmt.Errorf("create room: %w", err)
    }

    var room Room
    json.NewDecoder(resp.Body).Decode(&room)
    return &room, nil
}

func (c *Client) UpdateRoom(ctx context.Context, orgID, appointmentID int64, expiresAt time.Time) error {
    name := fmt.Sprintf("restartix-%d-%d", orgID, appointmentID)
    body := UpdateRoomRequest{
        Properties: RoomProperties{Exp: expiresAt.Unix()},
    }
    _, err := c.doRequest(ctx, http.MethodPost, "/rooms/"+name, body)
    return err
}

func (c *Client) DeleteRoom(ctx context.Context, orgID, appointmentID int64) error {
    name := fmt.Sprintf("restartix-%d-%d", orgID, appointmentID)
    _, err := c.doRequest(ctx, http.MethodDelete, "/rooms/"+name, nil)
    return err
}

Cross-Tenant Isolation ​

Problem ​

Room names are restartix-{appointmentID}. If a user guesses another appointment's ID, they could attempt to join the room.

Mitigation: Meeting Tokens ​

Daily.co meeting tokens ensure participants don't join rooms by name — they use short-lived tokens that grant access to a specific room.

go
// Room name now includes org ID for namespacing
func roomName(orgID, appointmentID int64) string {
    return fmt.Sprintf("restartix-%d-%d", orgID, appointmentID)
}

func (c *Client) CreateRoom(ctx context.Context, orgID, appointmentID int64, expiresAt time.Time) (*Room, error) {
    body := CreateRoomRequest{
        Name:    roomName(orgID, appointmentID),
        Privacy: "private",
        Properties: RoomProperties{
            MaxParticipants:   2,
            EnableScreenshare: true,
            EnableChat:        true,
            EnablePrejoinUI:   false,
            EnableNetworkUI:   false,
            EjectAtRoomExp:    true,
            Exp:               expiresAt.Unix(),
        },
    }
    // ...
}

// Meeting tokens are generated per-participant with room scope
func (c *Client) CreateMeetingToken(ctx context.Context, roomName string, userID int64, expiresAt time.Time) (string, error) {
    body := CreateTokenRequest{
        Properties: TokenProperties{
            RoomName:  roomName,
            UserID:    fmt.Sprintf("%d", userID),
            Exp:       expiresAt.Unix(),
            IsOwner:   false,
        },
    }
    resp, err := c.doRequest(ctx, http.MethodPost, "/meeting-tokens", body)
    if err != nil {
        return "", fmt.Errorf("create meeting token: %w", err)
    }
    var result struct{ Token string `json:"token"` }
    json.NewDecoder(resp.Body).Decode(&result)
    return result.Token, nil
}

Videocall Access Flow ​

  1. Appointment created → room created as restartix-{orgID}-{appointmentID} (private)
  2. Patient/specialist requests videocall access → Go API verifies they own the appointment (RLS) → generates meeting token scoped to that room
  3. Client uses meeting token to join — no room name guessing possible
  4. Max 2 participants enforced by Daily.co

Security Rule ​

The Go API never returns the room name to the client. It only returns meeting tokens. The client SDK uses the token to join.

Business Associate Agreement (BAA) ​

Daily.co is a HIPAA-compliant service. Ensure a Business Associate Agreement is in place before using Daily.co in production for any PHI-related videocalls.

Testing ​

go
func TestDailyCoRoomIsolation(t *testing.T) {
    room, _ := dailyClient.CreateRoom(ctx, 1, 102, expiry)
    assert.Equal(t, "restartix-1-102", room.Name) // Includes org ID

    // Meeting token is scoped to specific room
    token, _ := dailyClient.CreateMeetingToken(ctx, room.Name, 42, expiry)
    assert.NotEmpty(t, token)
    // Token can only join "restartix-1-102", not "restartix-2-102"
}