Licensed VPN features
Fifteen VPN features are gated by your SDK license. A setter for a feature you are not licensed for returns a refusal and persists nothing, so the value can never reach the tunnel. The tunnel checks again when it starts, and falls back to the licensed default for anything it is not licensed for.
Check what your license grants with KapeSdkHandle.licenseState — use it to hide UI rather than
letting a control fail when tapped. See
Licensed features for why that is a flow rather than a
synchronous read.
The features
| Feature | Gated entry point | Without the license |
|---|---|---|
VpnProtocolLightway | setSelectedProtocol(Lightway) | Refuses; Automatic skips Lightway endpoints |
VpnProtocolWireGuard | setSelectedProtocol(Wireguard) | Refuses; Automatic skips WireGuard endpoints |
VpnProtocolOpenVpn | setSelectedProtocol(OpenVPN) | Refuses; Automatic skips OpenVPN endpoints |
VpnProtocolObfuscation | — | Obfuscated endpoints are dropped, no refusal. Separate from the user-facing amneziaEnabled toggle, which only narrows further |
VpnLightwayBitripple | setBitrippleEnabled | Refuses; tunnel uses false |
VpnLightwaySni | setLightwaySNI | Refuses; tunnel uses the SDK default SNI |
VpnHttpsProxy | setProxyConfig | Refuses; tunnel uses no proxy |
VpnKillSwitchAdvanced | setKillSwitchMode(Advanced) | Refuses; tunnel uses Standard |
VpnSplitTunnel | setSplitTunnelAppMode, addApplicationToCurrentSplitTunnelMode, removeApplicationFromCurrentSplitTunnelMode, resetCurrentSplitTunnelMode | Refuses; every app is tunnelled |
VpnCustomDns | setCustomDnsServers | Refuses; tunnel uses the per-protocol default |
VpnAdvancedProtection | setAdvancedProtectionCategories | Refuses; tunnel applies no filtering |
VpnLightwayHeartbeat | setHeartbeatEnabled | Refuses; tunnel uses false |
VpnLightwayExpresslane | setExpresslaneEnabled | Refuses; tunnel uses false |
VpnSessionPauseResume | pause(duration) | Refuses. resume() is never gated, so a pause can always be released |
VpnAutoConnectRules | setConnectOnDemand, setOnDemandRules, addOnDemandRule, removeOnDemandRule, resetOnDemandRules, setConnectOnStartup | Refuses; no automatic connect or disconnect. Always-on VPN is a separate system setting the user controls |
setSelectedProtocol(Automatic) is always allowed — it is narrowed to the protocols you are
licensed for when the tunnel builds its endpoint list.
Handling a refusal
The gated setters are suspend — they wait for the license verdict — and return a sealed result.
Most return KapeVpnSettingResult, which names the feature:
when (val result = vpn.setSplitTunnelAppMode(KapeSplitTunnelAppMode.Allow(packages))) {
is KapeVpnSettingResult.Success -> Unit
is KapeVpnSettingResult.NotLicensed -> showError("Not licensed: ${result.feature.name}")
}
The settings that already had their own result type gained a NotLicensed case instead, so their
validation errors keep their existing shape:
| Setter | Result |
|---|---|
setLightwaySNI | KapeLightwaySNIResult.NotLicensed |
setCustomDnsServers | KapeCustomDnsResult.NotLicensed |
| the three split-tunnel package edits | KapeSplitTunnelEditResult.NotLicensed |
pause(duration) | PauseResult.NotLicensed |
Nothing is persisted when a setter refuses, so there is no state to roll back.
transport, cipher, deferConnectTimeout and amneziaEnabled are still assignable vars: they
carry no license key, so they can never refuse. amneziaEnabled reports your raw preference even
on an SDK without VpnProtocolObfuscation, where obfuscated endpoints are dropped anyway — see
WireGuard Obfuscation (Amnezia).
Reads report what the tunnel will do
The matching flows — selectedProtocolFlow, killSwitchModeFlow, bitrippleEnabledFlow,
heartbeatEnabledFlow, expresslaneEnabledFlow, lightwaySNIFlow,
advancedProtectionCategoriesFlow, customDnsServersFlow, splitTunnelAppModeFlow,
proxyConfigsFlow, connectOnDemandFlow, onDemandRulesFlow, connectOnStartupFlow — never fail. On an unlicensed SDK they emit the licensed default, so your UI
shows what the tunnel will actually apply rather than a value it will ignore.
selectedProtocolFlow reports Automatic when the persisted protocol is no longer licensed, and
killSwitchModeFlow reports Standard in place of Advanced.
They emit the licensed default while the verdict is still resolving too, and re-emit the real value once it lands — so a gate is never open before the license has been checked.
This matters after a license downgrade or expiry: a value persisted by a build with a broader license is never re-set through a setter, so the flows and the tunnel are what correct it.
Obfuscation is different
Obfuscation filters rather than refuses. Without VpnProtocolObfuscation, obfuscated endpoints are
dropped from the connection's pecking order and the connection proceeds on the remaining ones — so
Automatic still connects. Nothing is refused, and each dropped endpoint is logged.
If a location has only obfuscated endpoints, the connection fails. The tunnel reports
KapeVpnTunnelError.EndpointsExcludedByLicense on KapeVpnManager.lastTunnelError — distinct from
a location that serves no endpoints at all, so you can name the license as the cause. An Anonymous
Dedicated IP connection reports the same error when VpnProtocolLightway is missing, since it is
Lightway-only.
Expiry and revocation
The verdict comes from the Low Level SDK's licenseStatus(), which verifies the signature and reads
valid_until. Any problem grants nothing — an expired or revoked license refuses every gated
feature, exactly as an absent one does. KapeSdkHandle.licenseProblems (also on
KapeLicenseState.Resolved.problems) says which:
for (problem in handle.awaitLicenseState().problems) {
when (problem) {
is KapeLicenseProblem.Expired -> renewPrompt(problem.validUntil)
is KapeLicenseProblem.Revoked -> contactSupport(problem.licensedTo)
else -> log(problem.toString())
}
}
A license the Low Level SDK rejects outright never reaches licenseStatus() — building its manager
fails first, and every call that needs it fails with it.
Scope
These checks stop a feature being used by mistake, and they are backed by a signature the Low Level SDK verified. They are not a tamper-proof control inside a process an attacker controls; the Low Level SDK's own per-request enforcement remains the authority.
Editing the license is self-defeating: a license that fails verification makes every call that needs the manager fail — sign-in, location discovery and connecting included.