Skip to content
The mobile SDKs are available on request. Email support@grona.ai to get access.

Preview links

This page lets testers open a campaign variant on their own device before it goes live. A preview link carries a one-time pairing code. The SDK exchanges it for a signed pin bound to that install, so the tester sees the variant and nobody else’s experience changes. Your app routes the link to Grona.handleLink.

Preview links use your app’s own URL scheme with the host grona-preview:

Link Effect
acmemarket://grona-preview?code=<pairing code> Pairs this install with one campaign variant.
acmemarket://grona-preview?clear=1 Removes every preview pin on this device.

A code is 8 to 64 characters of letters, digits and hyphens. The SDK rejects a link with any other host, a port, user info, a path other than empty or /, a fragment, an extra or repeated parameter, or a query longer than 256 characters.

  1. Add an intent filter for your scheme and the grona-preview host to the activity that handles deep links:

    app/src/main/AndroidManifest.xml
    <activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTask">
    <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="acmemarket" android:host="grona-preview" />
    </intent-filter>
    </activity>
  2. Pass the link to Grona.handleLink from onCreate and onNewIntent:

    MainActivity.kt
    import ai.grona.sdk.Grona
    import android.content.Intent
    import android.os.Bundle
    class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // A launch from recents carries the old link; do not replay it
    val fromHistory = intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0
    if (savedInstanceState == null && !fromHistory) handleDeepLink(intent)
    }
    override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    handleDeepLink(intent)
    }
    private fun handleDeepLink(intent: Intent?) {
    val uri = intent?.data ?: return
    if (Grona.handleLink(uri)) return // a Grona preview link, accepted or not
    openAppLink(uri) // your own deep-link routing
    }
    }
  3. Set a previewUrl in your options. Grona provides it for your workspace. Without it the SDK ignores codes and can only clear pins.

handleLink returns true for any link whose host is grona-preview, whether or not the SDK accepted it, and false for every other link. A rejected link is logged through your GronaLogger.

When a code arrives, the SDK posts the code, the install id and the workspace key over https to previewUrl, and stores the signed pin it gets back.

  • Pairing is refused while consent is DENIED, because the request carries the install id. Under UNKNOWN and GRANTED it runs: pairing is a tester action, not analytics.
  • One exchange runs at a time. A link that arrives while one is in flight is ignored.
  • Any response other than a valid pin leaves existing pins unchanged.
  • Lifetime. A pin lasts until it expires (at most 24 hours), until a clear=1 link, until Grona.reset(), or until consent becomes DENIED. It survives app restarts and is verified again on every load.
  • One per campaign. The newest pin for a campaign wins.
  • What it bypasses. The pinned campaign goes first in its placement and skips audience rules, holdouts, the traffic split and activation.
  • What it never bypasses. A campaign disabled in the configuration (enabled: false), an expired configuration, or validation. A customer-unit campaign, such as every offer, still shows only to an identified shopper.
  • Control variants. A pinned control variant renders nothing, as any control arm does.
  • Marking. SDK components draw a “Preview” chip, decisions carry isPreview = true, and every event carries a preview flag so analysis excludes it.
  • No grants. A pinned offer renders without a grant, but it never appears in Grona.activeTreatments(). Pins never produce a grant, so a preview can never change what an order costs.

Debuggable builds can also pin a variant locally from the debug view, without a link. Local pins are unsigned, last at most 24 hours, and are ignored in apps that are not debuggable.