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

Events and exposure

The SDK records two kinds of events: campaign events it fires on its own as campaigns are decided, shown and used, and commerce events your app forwards. This page lists them, explains when exposure counts in each arm, and shows how Grona analyses results, including offers that need a grant. Read it before you design an experiment or check numbers from your own data.

Type Fired by When Arms Data
eligible SDK A decision is computed for a subscribed placement All Offers, treatment arm only: delivery, and grant_jti when granted
served SDK A treatment first reaches its slot view or listener Treatment Offers only: delivery, and grant_jti when granted
anchor_visible SDK The anchor’s top edge has been on screen for one continuous second All None
exposure SDK Half the content has been visible for one continuous second Treatment None
action SDK The shopper tapped a call to action, or your app called a decision action Treatment action (add, add_all, replace, navigate), product_ids, result
dismiss SDK The shopper dismissed content Treatment None
view_item Your app A product detail view Not a campaign event product_id, price_minor, for session products only
add_to_cart Your app Units added to the basket Not a campaign event qty; product_id and price_minor for session products only
remove_from_cart Your app Units removed from the basket Not a campaign event qty; product_id for session products only
begin_checkout Your app Checkout started Not a campaign event total_minor
purchase Your app Order placed Not a campaign event order_id, total_minor, currency, campaign_product_ids, treatments

arm is treatment, control, holdout or global_holdout. Money is in minor units of the basket currency, so total_minor: 1299 in USD is 12.99.

Every event also carries a random event_id (the server removes duplicates on it), a per-install seq counter that reveals gaps from loss, the time, a session_id, the install_id, the customer_id when the shopper is identified, and the config_version the decision came from. Campaign events add campaign_id, variant_id, arm, unit, placement and key. A new session starts after the app has spent 30 minutes in the background.

Events recorded while a preview pin is active carry preview: true, and events in demo mode carry demo: true. Both are excluded from analysis.

An experiment compares the treatment arm with the control arm. Control shoppers see nothing, so Grona measures exposure in a way that does not depend on content.

  • eligible is the analysis population. It fires once per campaign, placement, key and screen visit when a decision is computed, through the same code path for every arm.
  • anchor_visible measures the place, not the content. It fires for every arm when the placement’s anchor has had its top edge inside the visible screen for one continuous second, while the window is visible and focused. A dialog on top or the app moving to the background stops the second. Every arm measures the same anchor: the slot view, or the view your app passed to the decision call.
  • Empty anchors stay measurable. An anchor with nothing to show stays visible at zero height, and a zero-width or zero-height anchor still counts when its top edge is on screen.
  • exposure is diagnostic only. It follows the mobile ad viewability standard (half the content for one second), but control has no content, so it cannot be compared across arms.
  • served marks delivery. It fires once per campaign, placement, key and screen visit when a treatment first reaches an attached slot view (within the per-screen cap) or a listener receives a non-null decision.

A screen visit is keyed on placement, screen and key, not on the view, so list rows that rebind across views log each of these events once. See Patterns for host-rendered decisions for visits and the 2-second grace period.

An offer renders only with a grant, so a shopper assigned to its treatment arm may never see it. The delivery field on offer events says what happened:

delivery Meaning
granted A verified grant matched; grant_jti names it
grants_pending No grant fetch has completed since the shopper was identified
no_grant A fetch completed and no grant matched, or the app has no grant source
demo Demo mode rendered it without a grant
preview A preview pin rendered it

eligible keeps the assigned arm. Its delivery is the state at the first decision of the visit, which is often grants_pending, because grants are fetched in the background after sign-in. If a grant lands later in the same visit, the offer replaces whatever the placement was showing and logs served with delivery: granted, not a second eligible.

  • Intent to treat uses eligible. Compare the treatment arm with the control arm over everyone assigned, whether or not they saw content.
  • The treated share uses served with delivery: granted. For offers, that is the share of the treatment arm that actually saw the offer. Control can never receive an offer, so non-compliance is one-sided, and the effect on the treated is the intent-to-treat effect divided by that share.
  • Do not count treated shoppers from grant issuance. A grant can be minted and still fail to reach the device, arrive after the shopper left, or expire first. Join on grant_jti to tie exposure, issuance and redemption together.
  • Revenue truth comes from your order system. Client purchase events can be spoofed and are used only for joining. Send orders to Grona server to server; see Report revenue.

The SDK sends the least data that still lets Grona measure campaigns.

  • A session product is a product that a Grona campaign showed or added in the current session.
  • view_item, add_to_cart and remove_from_cart are always counted, but they carry the product id and price only for session products. For any other product those fields are left out.
  • purchase carries the order id, total, currency and applied treatments, and in campaign_product_ids only the session products in the order. Your app passes the whole order; the SDK keeps only the session products. The full basket and browsing history never leave the device.
  • Exception: on keyed placements such as cart_line, campaign events carry the line’s product id as their key, in every arm. So while a cart_line campaign is live, the product ids of the cart lines bound to it leave the device.
  • Events carry no search text, names, phone numbers or email addresses. customer_id is your opaque internal id.

See Consent and data minimisation for what is kept and sent in each consent state.

  • When. While the app is in the foreground, the SDK sends every 30 seconds, as soon as 20 events are queued, and when the app moves to the background.
  • Batch size. At most 50 events and 64 KB before compression per batch, sent gzip-compressed over HTTPS.
  • Queue limit. The on-disk queue holds at most 1,000 events or 7 days, oldest dropped first. This limit is applied at every start, in every consent state.
  • Retries. At most 3 retries with exponential backoff and full jitter. The SDK honours Retry-After on 429 and 503 responses and drops a batch on any other 4xx.
  • Clocks. Timestamps are Unix milliseconds from the device clock. The server corrects clock skew using its receive time.

Without an events endpoint configured, the SDK sends no events and keeps no event queue. In debug builds, the debug view shows the most recent events from memory.