Skip to main content

VPN Protocol

The SDK supports three VPN protocols, plus an Automatic mode that tries all three:

ProtocolDescription
AutomaticTries WireGuard, OpenVPN and Lightway in the Low Level SDK's pecking order — the default
WireguardWireGuard
LightwayLightway — ExpressVPN's proprietary protocol, requires a lightway-certificate-asset entry in kape_sdk_settings.xml
OpenVPNOpenVPN — broadly compatible TLS-based VPN, requires four openvpn-* entries in kape_sdk_settings.xml

Bundled Protocols​

Each protocol is opt-in: the app depends on one artifact per protocol it supports (see Installation) and passes its object to KapeVpnManager, so only the tunnel native libraries of those protocols ship in the APK.

ProtocolArtifactPass
WireGuardkape-platform-sdk-wireguardKapeWireGuardSupport
Lightwaykape-platform-sdk-lightwayKapeLightwaySupport
OpenVPNkape-platform-sdk-openvpnKapeOpenVpnSupport
val vpn = KapeVpnManager(handle, protocols = listOf(KapeLightwaySupport, KapeWireGuardSupport))
  • Under Automatic, endpoints of protocols you did not pass are skipped; the others keep the Low Level SDK's pecking order.
  • Selecting a protocol you did not pass fails the connection with KapeVpnTunnelError.ProtocolNotBundled on lastTunnelError.
  • An Anonymous Dedicated IP is Lightway-only: without KapeLightwaySupport, connect(location) returns ConnectResult.DipError(ProtocolNotBundled) under Automatic or Lightway, and DipError(DipUnsupportedProtocol) under another protocol.
  • Settings for a protocol you did not pass (Lightway cipher, Amnezia, …) are still stored, but have no effect.
  • Location lists under Automatic may include locations served only by a protocol you did not pass.

Reading the Selected Protocol​

The selection is a property, with a StateFlow for observing it:

val current = vpn.selectedProtocol
// current is KapeVPNProtocol — Automatic, Wireguard, Lightway, or OpenVPN

// Or observe it, e.g. from a ViewModel:
vpn.selectedProtocolFlow.collect { println(it) }
info

The default protocol is KapeVPNProtocol.Automatic if no selection has been persisted yet.

Changing the Protocol​

vpn.setSelectedProtocol(KapeVPNProtocol.OpenVPN)

The selection is written to the shared DataStore and is read by KapeVpnService on its next startup. Changes apply to the next VPN connection. If the tunnel is currently active, disconnect and reconnect to use the new protocol:

vpn.setSelectedProtocol(KapeVPNProtocol.OpenVPN)

if (vpn.connectionStatus.value != KapeVPNConnectionStatus.Disconnected) {
vpn.disconnect()
}

vpn.connect(KapeConnectionMode.Auto)

Lightway Requirements​

Using Lightway requires a lightway-certificate-asset entry in kape_sdk_settings.xml. Because XML attribute values are newline-normalised and cannot hold a multi-line PEM body, place the certificate file in assets/ and reference it by file name:

<kape-sdk-settings
...
lightway-certificate-asset="lightway_ca.pem" /> <!-- assets/lightway_ca.pem -->

The certificate is the CA certificate used to verify the Lightway server connection. It is provided to you by Kape. If this entry is missing and the Lightway protocol is selected, the tunnel will fail to start.

Network Transport​

Lightway and OpenVPN servers are reachable over either UDP or TCP. By default the SDK takes whatever the server offers; transport pins it to one.

vpn.transport = KapeVPNTransport.TCP

// Or observe it:
vpn.transportFlow.collect { println(it) }
TransportBehaviour
AutomaticThe server decides — the SDK default
UDPOnly UDP endpoints are attempted
TCPOnly TCP endpoints are attempted

vpn.transport is persisted to the shared DataStore and read by KapeVpnService on its next startup. Changes apply to the next VPN connection.

Which endpoints a selection affects:

Selected protocolEffect of a forced transport
Lightway, OpenVPNEndpoints not matching the transport are dropped
WireguardAlways resolves to UDP — WireGuard has no TCP variant, so a TCP selection cannot apply and the session runs over UDP
AutomaticNone — the selection is ignored entirely, so the Low Level SDK's protocol pecking order stays intact
info

Selecting TCP and then switching the protocol to Wireguard does not overwrite the stored selection — vpn.transport still reads TCP, so switching back to Lightway or OpenVPN restores your choice. Only the transport the session actually uses is resolved to UDP.

warning

The filter is strict — there is no fallback. If a forced transport matches no endpoint for the chosen location, no configuration is produced and the connection has nothing to attempt: the tunnel stays in Connecting and retries with backoff rather than silently using the other transport. Pair a forced transport with a location you know serves it.

An Anonymous Dedicated IP connection also honours the transport — and, unlike the shared-pool path above, it honours it even when the protocol is Automatic, because Anonymous Dedicated IP is Lightway-only and so has no protocol pecking order to preserve. A Simple Dedicated IP follows the shared-pool rules. A forced transport with no matching Anonymous Dedicated IP configuration fails fast with ConnectResult.DipError(KapeVpnTunnelError.DipUnavailable) rather than retrying.

An HTTPS proxy outranks this setting: under an explicit Lightway protocol with a proxy configured, TCP is forced regardless of what transport is set to. See HTTPS Proxy.

Bitripple is a UDP-only codec, so forcing TCP leaves it inert.

Endpoint Obfuscation​

By default the SDK attempts every endpoint a location offers, obfuscated or not. obfuscation restricts the connection to one kind of obfuscation:

vpn.obfuscation = KapeVPNObfuscation.FM3

vpn.obfuscationFlow.collect { println(it) } // Automatic, None, FM1, FM2, FM3, or Amnezia
ObfuscationEndpoints attempted
AutomaticAll of them, obfuscated or not — the SDK default
NoneUnobfuscated endpoints only (OpenVPN endpoints count as unobfuscated unless they carry an XOR value, which the SDK applies)
FM1, FM2, FM3Lightway endpoints using that FluffyMuffins version only
AmneziaWireGuard Amnezia endpoints only

vpn.obfuscation is persisted to the shared DataStore and read by KapeVpnService on its next startup. Changes apply to the next VPN connection.

Unlike the transport, the obfuscation applies under every protocol, Automatic included. It never reorders endpoints, so the Low Level SDK's pecking order is kept among the ones that remain. Pair it with a compatible protocol: Lightway + Amnezia, or Wireguard + FM3, matches nothing.

warning

Like the transport filter, this is strict — there is no fallback. If no endpoint of the chosen location uses the selected obfuscation, no configuration is produced and the tunnel stays in Connecting, retrying with backoff. FM3 in particular is only deployed to a few locations.

It combines with the other obfuscation controls rather than overriding them:

  • Amnezia with amneziaEnabled = false matches nothing — the toggle still wins.
  • obfuscation carries no license key, so setting it never refuses. Without VpnProtocolObfuscation on your license, obfuscated endpoints are dropped regardless, so an obfuscated selection fails with KapeVpnTunnelError.EndpointsExcludedByLicense. See Licensed VPN features.
  • An Anonymous Dedicated IP connection is always unobfuscated: it honours Automatic and None, and any other selection fails fast with ConnectResult.DipError(KapeVpnTunnelError.DipUnavailable). A Simple Dedicated IP's endpoints go through the same filter as a regular location's.

WireGuard Obfuscation (Amnezia)​

Some WireGuard endpoints are obfuscated with Amnezia, which disguises the handshake so it is harder to identify and block. They are used automatically where a location offers them. Disable the toggle to skip them and connect over plain WireGuard instead:

vpn.amneziaEnabled = false

vpn.amneziaEnabled is persisted to the shared DataStore and read by KapeVpnService on its next startup. WireGuard only, and changes apply to the next VPN connection. Defaults to true. Observe it with vpn.amneziaEnabledFlow.

This setting carries no license key, so it never refuses — it can only narrow what your license already allows. An SDK without VpnProtocolObfuscation drops obfuscated endpoints regardless of this toggle; see Licensed VPN features.

warning

Like the transport filter, this is strict — there is no fallback. If a location serves only Amnezia endpoints and the toggle is off, no configuration is produced and the tunnel stays in Connecting, retrying with backoff, rather than falling back to an obfuscated endpoint.

Lightway Cipher​

Lightway can use either AES or ChaCha20 as its cipher. AES is the SDK default.

vpn.cipher = KapeLightwayCipher.ChaCha20

vpn.cipher is persisted to the shared DataStore and read by KapeVpnService on its next startup. Lightway only, and changes apply to the next VPN connection. null (the default) falls back to KapeLightwayCipher.AES.

enum class KapeLightwayCipher {
AES,
ChaCha20,
}

Lightway Expresslane​

Lightway can optionally enable Expresslane, an acceleration feature. Disabled by default.

vpn.setExpresslaneEnabled(true)

vpn.expresslaneEnabled is persisted to the shared DataStore and read by KapeVpnService on its next startup. Lightway only, and changes apply to the next VPN connection. Defaults to false.

OpenVPN Requirements​

Using OpenVPN requires four PKI files, placed in assets/ and referenced from kape_sdk_settings.xml:

<kape-sdk-settings
...
openvpn-ca-certificate-asset="openvpn_ca_certificate.pem" <!-- CA chain -->
openvpn-client-certificate-asset="openvpn_client_certificate.pem"
openvpn-client-key-asset="openvpn_client_key.pem"
openvpn-tls-auth-key-asset="openvpn_tls_auth_key.pem" />

All four entries are optional — brands that do not ship OpenVPN can omit them. If OpenVPN is selected without configured PKI, the SDK surfaces a clear TunnelStartFailed error at connect time citing the missing entry, rather than silently falling back to a different protocol.

API Reference​

KapeVPNProtocol​

enum class KapeVPNProtocol {
Automatic,
Wireguard,
OpenVPN,
Lightway,
}

KapeVPNTransport​

enum class KapeVPNTransport {
TCP,
UDP,
Automatic,
}

KapeVPNObfuscation​

enum class KapeVPNObfuscation {
Automatic, // every endpoint, obfuscated or not
None, // unobfuscated endpoints only
FM1,
FM2,
FM3,
Amnezia,
}

KapeVpnManager​

/** The persisted protocol selection. Defaults to Automatic. Applies on the next connection. */
var selectedProtocol: KapeVPNProtocol

/** Observable form of [selectedProtocol]. */
val selectedProtocolFlow: StateFlow<KapeVPNProtocol>

/** The persisted transport selection. Defaults to Automatic. Applies on the next connection. */
var transport: KapeVPNTransport

/** Observable form of [transport]. */
val transportFlow: StateFlow<KapeVPNTransport>

/** The persisted obfuscation selection. Defaults to Automatic. Applies on the next connection. */
var obfuscation: KapeVPNObfuscation

/** Observable form of [obfuscation]. */
val obfuscationFlow: StateFlow<KapeVPNObfuscation>

/** WireGuard Amnezia obfuscation. Defaults to true. Applies on the next connection. */
var amneziaEnabled: Boolean

/** Observable form of [amneziaEnabled]. */
val amneziaEnabledFlow: StateFlow<Boolean>