Skip to main content
The Android VPN app is a native Kotlin / Jetpack Compose application that connects to the StartMyVPN backend over the same REST API used by the web frontend. It supports both WireGuard and OpenVPN, and ships with a guest-mode flow, Google Play Billing, AdMob, Firebase Analytics & Crashlytics, and TLS certificate pinning.
This app is a client for the StartMyVPN SaaS platform and will not function without it. You must have the StartMyVPN backend installed and running on your own server before this app can be used:StartMyVPN — WireGuard & OpenVPN SaaS Platform

Prerequisites

  • Android Studio Hedgehog (2023.1.1) or newer
  • JDK 11
  • Android SDK with API 26 (min) and API 36 (target) platforms installed
  • Android NDK 27.2.12479018 — required to build the OpenVPN native module
  • A Firebase project for Analytics and Crashlytics
  • A Google Play Console developer account (required to publish and to test in-app billing)
  • A deployed copy of the StartMyVPN backend reachable over HTTPS
  • The Android app source code (from your purchase)

Project Layout

app/
├── build.gradle.kts
├── google-services.json        (placeholder — replace with your own)
├── libs/
│   └── wireguard-tunnel.aar    (NOT included — see Step 2)
└── src/main/
    ├── AndroidManifest.xml
    ├── java/com/lunecore/vpn/
    │   ├── LuneVpnApp.kt       (Application class)
    │   ├── MainActivity.kt
    │   ├── ads/                (AdMob integration)
    │   ├── billing/            (Google Play Billing)
    │   ├── data/
    │   │   ├── local/          (EncryptedSharedPreferences)
    │   │   ├── model/          (User, Server, Plan, AppConfig)
    │   │   └── remote/         (Retrofit API + Repository)
    │   ├── ui/                 (Compose screens + ViewModels)
    │   └── vpn/                (VpnManager, VpnTunnelService)
    └── res/xml/
        └── network_security_config.xml

ics-openvpn/                    (OpenVPN library module, native C++ via CMake)

Step 1 — Open the Project

Open the project root folder in Android Studio. Let Gradle sync complete. The initial sync will fail until you complete Steps 2 and 3 below — that is expected.
Make sure the NDK version matches the one declared in app/build.gradle.kts. You can install it via Android Studio → SDK Manager → SDK Tools → NDK (Side by side).

Step 2 — Add the WireGuard Library

The WireGuard tunnel library (wireguard-tunnel.aar) is a native dependency that is not bundled with the release package. You can either:
  • Build it from source: wireguard-android — follow its build instructions to produce tunnel-release.aar and rename it to wireguard-tunnel.aar.
  • Or obtain a compatible prebuilt copy.
Place the final file at:
app/libs/wireguard-tunnel.aar
Without this file, Gradle sync will fail with an unresolved reference to com.wireguard.android:tunnel.

Step 3 — Configure Firebase

  1. Go to the Firebase Console and create a new project.
  2. Add an Android app with the package name you plan to use (e.g. com.yourcompany.vpn).
  3. Download google-services.json.
  4. Replace the placeholder file at app/google-services.json with your download.
The project uses Firebase for Analytics and Crashlytics. Without a valid google-services.json the project will not compile — this is enforced by the Google Services Gradle plugin.

Step 4 — Set the Package Name

Pick a unique Android application ID (reverse-DNS, all lowercase, e.g. com.yourcompany.vpn). Update it in all of the following places:

app/build.gradle.kts

android {
    namespace = "com.yourcompany.vpn"

    defaultConfig {
        applicationId = "com.yourcompany.vpn"
        // ...
    }
}

app/google-services.json

The client[].client_info.android_client_info.package_name field must match your applicationId exactly. If it does not, the Google Services plugin will throw an error at build time.

app/src/main/AndroidManifest.xml

Update the deep-link host (see Step 7) and any tools:replace references that include a package path.
You can use Android Studio’s Refactor → Rename on the com.lunecore.vpn Java package to rename the source tree in one pass. Do this before changing namespace and applicationId, then resync Gradle.

Step 5 — Configure the Backend URL

Open:
app/src/main/java/com/lunecore/vpn/data/remote/ApiClient.kt
Set your backend base URL:
private const val BASE_URL = "https://vpn.yourdomain.com/api/v1/"
The URL must end with /api/v1/ (including the trailing slash). Retrofit concatenates endpoint paths directly to this base.

Step 6 — Certificate Pinning

The app pins your backend’s TLS certificate via Android’s network security config to prevent MITM attacks. Open app/src/main/res/xml/network_security_config.xml and:
  1. Replace yourdomain.com with your actual API domain.
  2. Generate the SPKI SHA-256 hashes for your leaf certificate and its intermediate CA:
    # Leaf certificate pin
    openssl s_client -connect vpn.yourdomain.com:443 -servername vpn.yourdomain.com 2>/dev/null \
      | openssl x509 -pubkey -noout \
      | openssl pkey -pubin -outform DER \
      | openssl dgst -sha256 -binary \
      | base64
    
  3. Replace the placeholder YOUR_LEAF_CERT_PIN_HASH and YOUR_CA_CERT_PIN_HASH values with your computed hashes.
You should always pin two hashes: the current leaf (or its SPKI) and the issuing intermediate. When your certificate rotates, the intermediate will usually still match, preventing a hard outage.

The app handles password-reset and email-verification deep links of the form:
https://vpn.yourdomain.com/reset-password/{token}?email=user@example.com
In app/src/main/AndroidManifest.xml, find the <intent-filter> with <data android:host="..."/> and set the host to your domain:
<data android:scheme="https" android:host="vpn.yourdomain.com"/>
For Android App Links to verify automatically (no “Open with” dialog), your web server must serve a Digital Asset Links file at:
https://vpn.yourdomain.com/.well-known/assetlinks.json
The backend generates this automatically once you’ve added your app’s SHA-256 signing fingerprint in the admin panel.

Step 8 — AdMob (optional)

If you want to display ads:
  1. Create an AdMob account and register your Android app.
  2. Update the AdMob App ID in app/src/main/AndroidManifest.xml:
    <meta-data
        android:name="com.google.android.gms.ads.APPLICATION_ID"
        android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" />
    
  3. Update the individual ad unit IDs in:
    • app/src/main/java/com/lunecore/vpn/ads/AdConfig.kt
    • app/src/main/java/com/lunecore/vpn/ads/InterstitialAdManager.kt
    • app/src/main/java/com/lunecore/vpn/ads/AppOpenAdManager.kt
If you do not want ads at all, set the ad unit IDs to empty strings and remove the <meta-data> entry — or simply toggle ads_enabled to false on your backend’s /api/v1/app-config endpoint to hide the ad UI server-side without any code changes.

Step 9 — Google Play Billing

In-app purchases are mapped to your backend’s plan catalog. To wire them up:
  1. In Google Play Console, create your in-app products or subscriptions.
  2. Use product IDs that match the ones you configure on your backend’s plans (or map them in the admin panel).
  3. Upload a signed release build to an internal test track before testing billing — Google Play Billing does not work on debug builds distributed outside Play.
The backend verifies purchase tokens server-side via the Google Play Developer API, so make sure you’ve configured a service account and granted it access in Play Console → API access.

Step 10 — App Branding

App Name

Change the app_name string resource in:
app/src/main/res/values/strings.xml
Also update the localized copies in values-* folders if you want a different translated name.

App Icon

Replace the launcher icons under app/src/main/res/mipmap-*/ with your own. The easiest way is to use Android Studio’s File → New → Image Asset wizard with a 1024×1024 master icon.

Theme Colors

The Material 3 color scheme lives at:
app/src/main/java/com/lunecore/vpn/ui/theme/
Edit Color.kt and Theme.kt to match your brand palette.

Splash / Hero imagery

Brand imagery used on the home screen and onboarding is under app/src/main/res/drawable/. Replace PNGs and vector assets as needed.

Step 11 — Build & Sign a Release

1. Create a keystore

keytool -genkey -v -keystore release.jks \
    -keyalg RSA -keysize 2048 -validity 10000 \
    -alias your-alias
Store this file outside the project directory and back it up. If you lose it, you cannot publish updates to the app on Google Play.

2. Configure signing

In Android Studio: Build → Generate Signed Bundle / APK → Android App Bundle, then either paste the keystore path each time or create a signingConfig in app/build.gradle.kts that reads from a local keystore.properties file (never committed).

3. Build an AAB

./gradlew :app:bundleRelease
The output will be at app/build/outputs/bundle/release/app-release.aab.

4. Upload to Google Play

  1. Create your app in the Play Console.
  2. Upload your first AAB to an internal test track.
  3. Fill out the store listing, content rating, data safety form, and privacy policy URL.
  4. Once internal testing is stable, promote to closed / open testing, then production.
VPN apps go through additional policy review on Google Play. You must truthfully fill out the “VPN service” declaration in the app content section. Apps that use the VpnService API for anything other than providing a user-facing VPN will be rejected.

Step 12 — Backend Requirements

The Android client consumes the REST API exposed by the StartMyVPN backend. The key endpoints it calls are:
EndpointPurpose
POST /api/v1/auth/loginEmail/password login
POST /api/v1/auth/registerRegistration
POST /api/v1/auth/guestGuest / anonymous device login
POST /api/v1/auth/claimConvert a guest account to a full account
POST /api/v1/auth/forgot-passwordSend password-reset email
POST /api/v1/auth/reset-passwordConsume password-reset token
POST /api/v1/auth/resend-verificationResend the email-verification mail
PUT /api/v1/auth/change-emailChange email address
GET /api/v1/userCurrent user + active service
PUT /api/v1/account/passwordChange password
GET /api/v1/serversFull server list
GET /api/v1/free-serversFree-tier servers
GET /api/v1/premium-serversPremium-tier servers
GET /api/v1/servers/{id}/wireguardOn-demand WireGuard config
GET /api/v1/user/openvpn-credentialsOpenVPN username/password
GET /api/v1/plansPlan catalog
POST /api/v1/iap/verifyPlay Billing purchase verification
POST /api/v1/iap/guest-verifyPlay Billing verification for guests
POST /api/v1/iap/restoreRestore a previous purchase
GET /api/v1/app-configRuntime feature flags (ads, maintenance, force-update)
All of these endpoints are implemented by the StartMyVPN backend out-of-the-box. If you have customized any of these paths on your backend, update the Retrofit interface in:
app/src/main/java/com/lunecore/vpn/data/remote/ApiService.kt

Troubleshooting

You have not placed wireguard-tunnel.aar at app/libs/wireguard-tunnel.aar. See Step 2.
You have not replaced the placeholder app/google-services.json with a real file from your Firebase project. See Step 3.
Install NDK 27.2.12479018 via Android Studio → SDK Manager → SDK Tools → NDK (Side by side). The OpenVPN module uses CMake and requires this exact NDK version or newer.
Check that your server’s WireGuard or OpenVPN config is valid by testing it on desktop first. Also verify that the API returns a config string in the expected format on GET /api/v1/servers/{id}/config.
You are either testing on a debug build (billing requires a signed build distributed via Play), your product IDs do not match what is configured in Play Console, or the account you are testing with is not on a test/license list in Play Console.
Your pins in network_security_config.xml do not match the actual certificate chain served by your domain. Regenerate with openssl and verify by running the app in debug with OkHttp logging enabled.

Quick Checklist

1

WireGuard AAR

Place wireguard-tunnel.aar at app/libs/.
2

Firebase

Replace app/google-services.json with your own.
3

Package name

Update namespace and applicationId in app/build.gradle.kts and in google-services.json.
4

Backend URL

Set BASE_URL in ApiClient.kt.
5

Cert pinning

Update domain + SPKI hashes in network_security_config.xml.
6

Deep links

Update the host in the <intent-filter> of AndroidManifest.xml.
7

AdMob

Update App ID and ad unit IDs (or disable ads server-side).
8

Branding

Update app_name, launcher icons, theme colors, and drawables.
9

Sign & build

Generate a keystore, create a signing config, and build an AAB.
10

Publish

Upload to Google Play internal testing, then promote to production.