> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getmelody.io/llms.txt
> Use this file to discover all available pages before exploring further.

# OTP via WebSocket

> Request a One-Time Password directly over an existing WebSocket connection, without a separate HTTP request.

The Melody device can request an OTP directly over its active WebSocket connection to the Haptics server. This is an alternative to the WiFi API `startOtp` service command and does not require a separate HTTP call.

***

## Frame Types

| Frame Type     | Value | Direction       | Protobuf Message |
| -------------- | ----- | --------------- | ---------------- |
| `OTP_REQUEST`  | `19`  | Device → Server | `OTPRequest`     |
| `OTP_RESPONSE` | `20`  | Server → Device | `OTPResponse`    |

***

## Message Definitions

**OTPRequest** (device → server):

```protobuf theme={null}
message OTPRequest {
  bool reserved = 1; // reserved for future fields (e.g. scope)
}
```

**OTPResponse** (server → device):

```protobuf theme={null}
message OTPResponse {
  string otp = 1;           // The OTP code
  uint64 expires_at_ms = 2; // Expiration timestamp in milliseconds (Unix)
  string error = 3;         // Non-empty if the request failed
}
```

***

## Flow

1. Device sends an `OTP_REQUEST` frame with a serialized (empty) `OTPRequest` message over the existing WebSocket connection.
2. Server generates the OTP and replies with an `OTP_RESPONSE` frame on the same connection.
3. If generation fails, `error` is non-empty — `otp` should be ignored in this case.

***

## Code Example

```go theme={null}
// --- Request ---

otpReq := &entities.OTPRequest{}
data, err := proto.Marshal(otpReq)
if err != nil {
    return err
}

frame := protocol.NewHapticFrame(entities.FrameType_OTP_REQUEST, data)
if err := frame.WriteFrame(stream); err != nil {
    return err
}

// --- Response ---

respFrame, err := protocol.ReadNextFrame(stream)
if err != nil {
    return err
}

if respFrame.Type != entities.FrameType_OTP_RESPONSE {
    return fmt.Errorf("unexpected frame type: %v", respFrame.Type)
}

otpResp := &entities.OTPResponse{}
if err := proto.Unmarshal(respFrame.Data, otpResp); err != nil {
    return err
}

if otpResp.Error != "" {
    return fmt.Errorf("OTP generation failed: %s", otpResp.Error)
}

fmt.Printf("OTP: %s, expires at: %d ms\n", otpResp.Otp, otpResp.ExpiresAtMs)
```

***

## Notes

* The OTP is scoped to the requesting device's connection — it is not broadcast to other subscribers.
* Maximum response time is 10 seconds (proxy timeout to the session manager).
* The `startOtp` WiFi API service command remains available as an alternative for devices that need OTP without an active WebSocket connection.
