Praman

Invitation links on Android

Every invitation Praman sends links to one Notils page. Configure your Android app once, and the link opens your app at the invitation, or installs it from Google Play and keeps the invitation through the install.

A project's invitation link starts as https://notils.com/i/<project id>/<token>, for example https://notils.com/i/acme-shop/pri_8Hc2…. A Development environment has its own prefix, /i/acme-shop-dev/. You can change the link on the project's Invitations page if you would rather host your own.

The person opens the link…What happens
On Android, app installedAndroid App Links open your app directly with the link. If the browser opens the page instead, its Open button launches your app.
On Android, app not installedThe page shows the invitation and sends them to Google Play with notils_invite=<token> in the install referrer. Your app reads it on first launch.
On a computer or iPhoneThe page shows a QR code to open the link on an Android phone.
Expired, used or revokedOne neutral message: the invitation can't be used, ask for a new one.

The page only shows the invitation. Accepting happens in your app, after the person signs in and proves the number or address it was sent to.

Configure your project

  1. Add the package and fingerprints in the dashboard

    Open the project in the dashboard, then Configure → Mobile app. Enter the Android package (com.acme.shop), the SHA-256 fingerprints of the keys your builds are signed with (up to 10), and your Play Store listing. Production and Development are configured separately, so a debug build can use its own package and key.

    For a release on Google Play, the fingerprint that matters is the app signing key: Play Console → your app → Test and release → App integrity → App signing → App signing key certificate → SHA-256. Add your upload key too if you test locally signed builds.

    Terminal
    # Your upload or debug key (Play App Signing's key is in Play Console)
    keytool -list -v -keystore ~/.android/debug.keystore \
      -alias androiddebugkey -storepass android | grep SHA256
    
    # Or, in an Android project
    ./gradlew signingReport
  2. Claim your path in the app

    Add an intent filter for your own project's prefix. Use the project id from the dashboard; Development builds use the -dev id.

    AndroidManifest.xml
    <activity android:name=".MainActivity" android:exported="true">
      <!-- Notils invitation links for this project only -->
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
          android:scheme="https"
          android:host="notils.com"
          android:pathPrefix="/i/acme-shop/" />
      </intent-filter>
    </activity>

    In an Expo app, the same filter goes in the app config:

    app.json
    {
      "expo": {
        "android": {
          "package": "com.acme.shop",
          "intentFilters": [
            {
              "action": "VIEW",
              "autoVerify": true,
              "category": ["BROWSABLE", "DEFAULT"],
              "data": [
                { "scheme": "https", "host": "notils.com", "pathPrefix": "/i/acme-shop/" }
              ]
            }
          ]
        }
      }
    }
  3. Read the token

    When the link opens your app, the token is the last path segment. Keep it until the person has signed in, then show the invitation and accept it.

    Kotlin
    // https://notils.com/i/acme-shop/pri_8Hc2…  →  "pri_8Hc2…"
    fun invitationToken(uri: Uri, projectId: String): String? {
        val segments = uri.pathSegments            // ["i", "acme-shop", "<token>"]
        if (segments.size != 3 || segments[0] != "i" || segments[1] != projectId) return null
        return segments[2]
    }
  4. Read the install referrer on first launch

    When someone installs from the invitation page, Google Play hands your app the referrer notils_invite=<token>. Read it once with the Play Install Referrer library and treat it exactly like an opened link.

    Kotlin
    // implementation("com.android.installreferrer:installreferrer:2.2")
    val client = InstallReferrerClient.newBuilder(context).build()
    client.startConnection(object : InstallReferrerStateListener {
        override fun onInstallReferrerSetupFinished(code: Int) {
            if (code == InstallReferrerClient.InstallReferrerResponse.OK) {
                // "notils_invite=pri_8Hc2…" (other keys may be present: split on &)
                val referrer = client.installReferrer.installReferrer
                val token = referrer.split("&")
                    .map { it.split("=", limit = 2) }
                    .firstOrNull { it.size == 2 && it[0] == "notils_invite" }
                    ?.let { Uri.decode(it[1]) }
                if (token != null) savePendingInvitation(token)   // once, on first launch
            }
            client.endConnection()
        }
        override fun onInstallReferrerServiceDisconnected() {}
    })

Check that Android verified your app

Notils publishes https://notils.com/.well-known/assetlinks.json from every project's package and fingerprints, refreshed within about five minutes of a change. After installing a build, ask Android to verify again and check the result:

Terminal
adb shell pm verify-app-links --re-verify com.acme.shop
adb shell pm get-app-links com.acme.shop
#   notils.com: verified

Why the whole host, but only your path

Android verifies a domain, not a path: it checks that your package and certificate are listed in assetlinks.json on notils.com. Which links your app then opens is decided by your intent filter. Claim only /i/<your project id>/, so your app never opens another app's invitations, and theirs never open yours.

Testing on staging

Notils' staging environment serves the same page and file from staging.notils.com. A build that talks to staging Praman needs android:host="staging.notils.com" in its filter.