Skip to main content
Version: 4.x.x

End-to-End Encryption (E2EE) - iOS

Overview​

End-to-end encryption (E2EE) ensures that your media content remains private and secure by encrypting it on the sender's device and decrypting it only on the receiver's device. This prevents intermediaries, including VideoSDK servers, from accessing or modifying the content.

E2EE is particularly important for applications in security-critical domains such as telehealth, finance, and legal services.

How E2EE Works​

  • E2EE is applied at the room level, where a single shared key encrypts and decrypts all media tracks during a session. You can also give each participant its own key, as shown in Per-Participant Keys.
  • Participants without the key cannot access the media.
  • The entire encryption process is handled client-side. VideoSDK does not access or store any encryption keys.

Key Management & Distribution​

You are fully responsible for generating, managing, and securely distributing encryption keys to all participants. VideoSDK never stores, accesses, or transfers your encryption keys.

  • Common Key Distribution Approaches:
    • Generate the key on your server when creating a room.
    • Send the key securely along with the room access token.
    • Use HTTPS-secured API calls to fetch the key on the client.
important

⚠️ Ensure that all participants have access to the correct encryption key before joining the room.

The table below shows the minimum SDK versions that support E2EE. All subsequent versions also include E2EE support.

React NativeiOSFlutterReactAndroidJavaScript
0.2.12.2.22.1.00.3.50.5.00.2.6

Enabling E2EE in VideoSDK iOS​

To enable E2EE in VideoSDK for iOS, set the key provider with VideoSDK.setKeyProvider(_:) before you call join(). setSharedKey(_:) throws, so call it with try. setKeyProvider(_:) does not throw.

private func createAndJoinRoom(customCameraTrack: CustomStreamTrack? = nil, customMicrophoneTrack: CustomStreamTrack? = nil) {
// keySetup() for enabling E2EE
guard keySetup() else { return }
// create the room
do {
room = try VideoSDK.createRoom(
roomId: roomData.roomId,
token: roomData.token,
participantName: roomData.name,
micEnabled: roomData.micEnabled,
camEnabled: roomData.cameraEnabled,
customCameraTrack: customCameraTrack,
customMicrophoneTrack: customMicrophoneTrack
)
} catch {
print("Error creating the room: \(error)")
return
}
// listener
room?.addEventListener(self)
// join
Task {
do {
try await room?.join()
} catch {
print("Error joining the room: \(error)")
}
}
}

// returns false if the key could not be set
func keySetup() -> Bool {
do {
let customKeyProvider = BaseKeyProvider()
try customKeyProvider.setSharedKey("<Your Encryption Key>")
VideoSDK.setKeyProvider(customKeyProvider)
return true
} catch {
print("Error setting encryption keys: \(error)")
return false
}
}
warning

setSharedKey throws ERROR_INVALID_PARAMETER for an empty key. The key is used exactly as you pass it.

To turn E2EE off for the next join, call VideoSDK.removeKeyProvider(). VideoSDK.getKeyProvider() returns the provider you set, or nil.

Per-Participant Keys​

Instead of one shared key, you can give each participant its own key. Each participant's media is then encrypted with that participant's key. Per-participant keys need iOS SDK 4.0.0 or later.

  • Set the key provider with setKeyProvider(_:), then add the keys with VideoSDK.setParticipantKey(_:key:) before you call join(). setParticipantKey throws ERROR_INVALID_PARAMETER (3073) if no key provider was set. The keys are saved in the provider, so a rejoin uses the same keys.
  • Pass each participant's own ID as participantId to VideoSDK.createRoom, and use those IDs here. The ID and key are used exactly as you pass them.
  • Every participant needs the keys of all participants, including its own. The media of a participant who has no key is not encrypted.
  • Participant keys are used only when no shared key is set.
func participantKeySetup() {
let keyProvider = BaseKeyProvider()
VideoSDK.setKeyProvider(keyProvider)
do {
// one key for each participant, including yourself
try VideoSDK.setParticipantKey("<PARTICIPANT_ID_1>", key: "<KEY_1>")
try VideoSDK.setParticipantKey("<PARTICIPANT_ID_2>", key: "<KEY_2>")
} catch {
print("Error setting participant keys: \(error)")
}
}

To change or remove a participant's key while in the room, call VideoSDK.setParticipantKey(_:key:) again. It takes effect at once. Pass nil to remove the key, or call removeKey(_:) on your key provider. After a key is removed, this device stops decrypting that participant until a new key is set. No event is raised: onE2EEStateChanged does not report decryption failures.

do {
// change a participant's key while in the room
try VideoSDK.setParticipantKey("<PARTICIPANT_ID_2>", key: "<NEW_KEY_2>")

// remove a participant's key
try VideoSDK.setParticipantKey("<PARTICIPANT_ID_2>", key: nil)
} catch {
print("Error updating participant keys: \(error)")
}

To Check Whether E2EE Is Enabled​

You can check whether E2EE is enabled by using e2eeEnabled property of Room class. It is set when you join, and it is true when the key provider you set with setKeyProvider(_:) holds a shared key or participant keys.

let isE2EEEnabled = room?.e2eeEnabled ?? false
print("Is E2EE Enabled: \(isE2EEEnabled)")

Limitations​

E2EE only applies to media. It does not apply to:

  • Chat messages or metadata
  • API calls and signaling data

These communications are still protected by TLS but are not encrypted end-to-end.

info

Recording and transcription features are not supported when End-to-End Encryption (E2EE) is enabled.

API Reference​

The API references for all the methods utilized in this guide are provided below.

Got a Question? Ask us on discord