VPN Protocol
The SDK supports three VPN protocols, plus an Automatic mode that tries all three:
| Protocol | Description |
|---|---|
Automatic | Tries WireGuard, OpenVPN and Lightway in the Low Level SDK's pecking order — the default |
Wireguard | WireGuard |
Lightway | Lightway — ExpressVPN's proprietary protocol, requires a lightway-certificate-asset entry in kape_sdk_settings.xml |
OpenVPN | OpenVPN — 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.
| Protocol | Artifact | Pass |
|---|---|---|
| WireGuard | kape-platform-sdk-wireguard | KapeWireGuardSupport |
| Lightway | kape-platform-sdk-lightway | KapeLightwaySupport |
| OpenVPN | kape-platform-sdk-openvpn | KapeOpenVpnSupport |
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.ProtocolNotBundledonlastTunnelError. - An Anonymous Dedicated IP is Lightway-only: without
KapeLightwaySupport,connect(location)returnsConnectResult.DipError(ProtocolNotBundled)underAutomaticorLightway, andDipError(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
Automaticmay 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) }
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) }
| Transport | Behaviour |
|---|---|
Automatic | The server decides — the SDK default |
UDP | Only UDP endpoints are attempted |
TCP | Only 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 protocol | Effect of a forced transport |
|---|---|
Lightway, OpenVPN | Endpoints not matching the transport are dropped |
Wireguard | Always resolves to UDP — WireGuard has no TCP variant, so a TCP selection cannot apply and the session runs over UDP |
Automatic | None — the selection is ignored entirely, so the Low Level SDK's protocol pecking order stays intact |
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.
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
| Obfuscation | Endpoints attempted |
|---|---|
Automatic | All of them, obfuscated or not — the SDK default |
None | Unobfuscated endpoints only (OpenVPN endpoints count as unobfuscated unless they carry an XOR value, which the SDK applies) |
FM1, FM2, FM3 | Lightway endpoints using that FluffyMuffins version only |
Amnezia | WireGuard 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.
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:
AmneziawithamneziaEnabled = falsematches nothing — the toggle still wins.obfuscationcarries no license key, so setting it never refuses. WithoutVpnProtocolObfuscationon your license, obfuscated endpoints are dropped regardless, so an obfuscated selection fails withKapeVpnTunnelError.EndpointsExcludedByLicense. See Licensed VPN features.- An Anonymous Dedicated IP connection is always unobfuscated: it honours
AutomaticandNone, and any other selection fails fast withConnectResult.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.
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>