Skip to main content

HTTPS Proxy

The SDK can route the VPN tunnel through one or more client-supplied HTTPS proxies. The proxy carries the Lightway-over-TCP handshake and tunnel: the proxy is applied to Lightway TCP endpoints only. WireGuard and OpenVPN endpoints are never proxied.

Proxies are supplied entirely by your app — the SDK has no built-in proxy list and no region-specific behaviour. Configuring one is a two-step flow: persist the proxies with setProxyConfig, then connect.

Setting a Proxy​

val vpn = KapeVpnManager(
handle,
protocols = listOf(KapeWireGuardSupport, KapeLightwaySupport, KapeOpenVpnSupport),
)

vpn.setProxyConfig(
KapeProxyConfig(
server = "proxy.example.com",
port = 443,
credential = KapeProxyCredential.UsernamePassword("user", "secret"),
certValidationMethod = KapeProxyCertValidationMethod.Automatic,
),
)

setProxyConfig is suspend: it waits for the license verdict, refuses with KapeVpnSettingResult.NotLicensed if VpnHttpsProxy is not granted, and otherwise updates the exposed flows and persists before returning. The selection is written to the shared DataStore and read by KapeVpnService on its next startup. Changes apply to the next VPN connection — reconnect to pick up a change mid-session:

vpn.setProxyConfig(config)

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

vpn.setSelectedProtocol(KapeVPNProtocol.Lightway)
vpn.connect(KapeConnectionMode.Auto)

Clearing a Proxy​

vpn.setProxyConfig(null) // or setProxyConfig(emptyList())

Clearing removes the persisted entry, including the stored secrets.

Credentials​

Authentication is modelled by KapeProxyCredential. The username is always a plain string supplied by your app; only the password may be token-sourced.

CasePassword source
KapeProxyCredential.UsernamePassword(username, password)Static, supplied by your app and persisted encrypted
KapeProxyCredential.TokenAuth(username)The Kape Client SDK connection token, resolved fresh at every connect — never persisted
// Static credentials
KapeProxyCredential.UsernamePassword("user", "secret")

// Password is the connection token, refreshed on every connection
KapeProxyCredential.TokenAuth("user")
info

TokenAuth needs a valid connection token at connect time. If the token cannot be fetched, Lightway endpoints are skipped and the connection falls through to the remaining endpoints.

Certificate Validation​

certValidationMethod controls how the proxy's TLS certificate is verified:

MethodBehaviour
OffNo verification
AutomaticVerified against the system trust store (default)
ManualVerified against the PEM supplied in certificate

certificate is sent only when the method is Manual; it is ignored otherwise.

vpn.setProxyConfig(
KapeProxyConfig(
server = "proxy.example.com",
port = 443,
credential = KapeProxyCredential.TokenAuth("user"),
certValidationMethod = KapeProxyCertValidationMethod.Manual,
certificate = context.assets.open("proxy_ca.pem").reader().readText(),
),
)

Interaction With Protocol and Transport​

The proxy applies only to Lightway TCP endpoints, so the selected protocol decides what happens to the rest of the endpoint list:

Selected protocolBehaviour with a proxy set
KapeVPNProtocol.LightwayTCP is forced — UDP Lightway endpoints are dropped so the proxy is never silently bypassed. Strict: if no TCP endpoint exists, the connection has nothing to attempt
KapeVPNProtocol.AutomaticEvery endpoint is kept in the Low Level SDK's pecking order; the proxy is applied in place on the Lightway TCP ones. Pass promoteUnderAutomatic = true to lead with the proxied Lightway TCP endpoints instead
KapeVPNProtocol.Wireguard, KapeVPNProtocol.OpenVPNNo effect — those endpoints are never proxied

Under Lightway, the forced TCP filter outranks vpn.transport — a KapeVPNTransport.UDP selection is discarded rather than silently bypassing the proxy. Under Automatic the transport selection is ignored anyway. See Network Transport.

// Strict: Lightway over TCP through the proxy, or nothing.
vpn.setSelectedProtocol(KapeVPNProtocol.Lightway)
vpn.setProxyConfig(config)

// Try proxied Lightway TCP first, then fall back through the normal pecking order.
vpn.setSelectedProtocol(KapeVPNProtocol.Automatic)
vpn.setProxyConfig(listOf(config), promoteUnderAutomatic = true)
info

The single-proxy convenience setProxyConfig(config) always uses promoteUnderAutomatic = false. Use the list form when you need to set the flag.

Multiple Proxies​

Passing more than one proxy spreads them round-robin across the Lightway TCP endpoints — the first proxied endpoint uses the first proxy, the second uses the second, wrapping as needed. All proxies in the list are used within a single connection attempt sequence.

vpn.setProxyConfig(
listOf(
KapeProxyConfig("proxy-a.example.com", 443, KapeProxyCredential.TokenAuth("user")),
KapeProxyConfig("proxy-b.example.com", 443, KapeProxyCredential.TokenAuth("user")),
),
)

Pre-resolved Proxy IPs​

Set resolvedIps to IP literals (IPv4 or IPv6) for server, and Lightway connects to those addresses instead of looking server up in DNS. That keeps the proxy reachable where its DNS name is blocked or poisoned.

Each IP is its own round-robin entry: a proxy with two IPs counts as two entries, with the same host, credential and certificate. An empty list (the default) means Lightway resolves server itself. The SDK does not validate the strings. A value that is not an IP fails that endpoint at connect time.

vpn.setProxyConfig(
listOf(
KapeProxyConfig(
server = "proxy.example.com",
port = 443,
credential = KapeProxyCredential.TokenAuth("user"),
resolvedIps = listOf("203.0.113.10", "203.0.113.11"),
),
),
)

Reading the Configured Proxies​

// Snapshot
val configs = vpn.proxyConfigs() // emptyList() when none configured
val promotes = vpn.promoteProxyUnderAutomaticFlow.value

// Observe (e.g. to drive a settings screen)
viewModelScope.launch {
vpn.proxyConfigsFlow.collect { println(it) }
}
info

proxyConfigsFlow is loaded from the DataStore asynchronously when KapeVpnManager is constructed, so it may briefly report emptyList() at startup. It exists to feed your UI — the tunnel re-reads the persisted value itself on every connect, so a connection is never affected by the flow's load timing.

Storage​

The whole proxy list — credentials and PEM certificate included — is serialised to JSON, encrypted with AES/GCM using an Android Keystore-backed key (alias kape_proxy_config_key), and stored Base64-encoded in the SDK's DataStore. A TokenAuth proxy stores no password; its connection token is fetched at every connect.

Limitations​

  • Lightway TCP only. WireGuard and OpenVPN endpoints ignore the proxy entirely.
  • Not applied to Anonymous Dedicated IP. An Anonymous Dedicated IP connection resolves a single Lightway endpoint through its own path; configured proxies are ignored and a warning is logged. A Simple Dedicated IP uses the proxy like any location.
  • Applies on the next connection. An active tunnel keeps its current proxy state until it reconnects.

API Reference​

KapeProxyConfig​

data class KapeProxyConfig(
val server: String,
val port: Int,
val credential: KapeProxyCredential,
val certValidationMethod: KapeProxyCertValidationMethod = KapeProxyCertValidationMethod.Automatic,
/** PEM certificate used only when [certValidationMethod] is [KapeProxyCertValidationMethod.Manual]. */
val certificate: String = "",
/** Pre-resolved IP literals for [server]; each is its own round-robin entry. */
val resolvedIps: List<String> = emptyList(),
)

KapeProxyCredential​

sealed class KapeProxyCredential {
data class UsernamePassword(
val username: String,
val password: String,
) : KapeProxyCredential()

/** Password is resolved at connect time from the connection token — never persisted. */
data class TokenAuth(
val username: String,
) : KapeProxyCredential()
}

KapeProxyCertValidationMethod​

enum class KapeProxyCertValidationMethod {
Off,
Automatic,
Manual,
}

KapeVpnManager​

/** Sets one or more HTTPS proxies. Empty list clears. Applies on next connection. */
suspend fun setProxyConfig(configs: List<KapeProxyConfig>, promoteUnderAutomatic: Boolean = false): KapeVpnSettingResult

/** Single-proxy convenience forwarding to the list form. `null` clears. */
suspend fun setProxyConfig(config: KapeProxyConfig?): KapeVpnSettingResult

/** Current snapshot of the configured proxies. */
fun proxyConfigs(): List<KapeProxyConfig>

/** Configured HTTPS proxies (Lightway-over-TCP only). Empty when none are set. */
val proxyConfigsFlow: StateFlow<List<KapeProxyConfig>>

/** Whether proxied Lightway-TCP endpoints lead under Automatic. */
val promoteProxyUnderAutomaticFlow: StateFlow<Boolean>