Skip to main content

Actual Activation Overview

What is BlinkEngage?​

BlinkEngage is the SDK for Actual's Activation Platform. This SDK sits on top of the BlinkReceipt SDK (Paper Receipt) and adds rewards, offers, and ad monetization to your receipt scanning flow. BlinkEngage takes over after the scan — loading screen, receipt summary, boost ads, the works.


What It Does​

Out-of-the-box User Activation Journeys​

The SDK supports five distinct user journeys, each representing a different way users can engage and earn rewards:

JourneyDescription
Offer Discovery & ClippingBrowse and clip available offers
Receipt ScanningScan receipts to earn on qualifying purchases
Receipt Boosters (Watch & Earn)Watch ads for bonus rewards
Product Capture UGC Tasks (Snap & Earn)Photograph products for extra rewards
Missed EarningsCorrect receipt data for potential credits

Offer Wall​

A pre-built screen that shows coupons and deals to your users. Supports grid, list, and carousel layouts and controlled server side by Actual with dynamic capabilities. Users can filter by store, clip offers to save them, and kick off a receipt scan from a floating action button.

The offer wall manages requirements for offer details and catalogue management on behalf of the publisher app. It should be presented in a regularly accessible location within the host app. Ideally dedication navbar location. To surface offers elsewhere in your app, such as a carousel on your home screen, use the Client-Side Offer Feed.

Two modes:

  • .all — Every available offer
  • .clipped — Only what the user saved

Client-Side Offer Feed​

The Client-Side Offer Feed lets you show the SDK's offers in your own UI, such as a carousel on your home screen or a section in your rewards tab. Your app asks the SDK for the feed and gets back the same curated offers the offer wall shows, as plain data: groups, each with its offers, ranked by Actual and with reward values already in your currency. When the shopper taps one of your cards, your app hands the offer or group back to the SDK, which opens its own offer details or the group's full list.

Your app owns the layout. The SDK still owns the data, the offer details, clipping, and scanning.

PlatformAvailable fromGuide
iOS1.14.0iOS Client-Side Offer Feed
AndroidComing in a future release—

See Starting from your app's own UI for how your app and the SDK hand the shopper back and forth.

Receipt Scanning​

Engage your users and collect valuable purchase data. Actual fully-manages the user experience and data extraction of scanning a paper receipt from any retailer and turning it into structured data.

Receipt Boosters and Product Capture​

Once a user successfully scans a receipt, we'll keep them engaged with more ways to earn (and drive revenue for you) such as watching targeted ads (Watch & Earn) or snapping product labels and barcodes (Snap & Earn).

Missed Earnings​

For users who have challenges with receipt submission or reward redemption, we provide an out-of-the-box flow for users to re-submit their receipt to claim any missed earnings.

Rewards​

You configure three properties and the SDK handles the rest:

  • currencyName — What to call your currency ("points", "coins", etc.). Default: "points"
  • currencyPerDollar — Conversion rate. Default: 100.0 ($1 = 100 points)
  • userPayoutPercentage — Share of boost/UGC rewards the user gets (40%–100%). Default: 0.6. In current versions, this is a shared property. Best practice guidance is to use the default 60%.

On iOS (1.12.0+), the base scan reward is set by your app unless Actual has configured an amount for the receipt's store. See Store-specific base scan reward.

Theming​

Implement the Theme protocol to swap out colors, fonts, and icons across every screen. Return nil for any key to keep the SDK default. See the iOS Activation Customization guide for the full reference of color, font, and icon keys.


How the Scan Flow Works​

1. User Scans a Receipt​

You start the BlinkReceipt camera as usual. When enableBlinkEngage = true, the SDK takes it from there — no need to handle scan results yourself.

2. Loading Screen​

While the backend processes the receipt, users see a loading drawer with a progress bar and an ad slot (native or banner). The progress runs in two stages: 0%→80% while waiting for data, then 80%→100% when results come back.

Status messages rotate through: "Scanning receipt", "Qualifying purchases", "Loading offers".

3. Receipt Summary Screen​

Shows what the user earned:

Reward TypeWhat It Is
Base Scan RewardPoints your app assigns for completing a scan (via rewardCallback), unless Actual has configured an amount for the receipt's store (details)
Webhook: base_scan
PVP Offer RewardsPoints earned (with statuses streamed back via webhook) for promotions related to purchased items on the receipt
Webhook: promo
Boost RewardsBonus points from watching a rewarded video ad
Webhook: boost
UGC Task RewardsPoints paid by Actual for scanning a product barcode and taking a photo
Webhook: barcode_collection

The reward counter animates from 0 to the total, followed by a confetti effect. Qualified products get highlighted in the list.

4. Boost Opportunities​

After the summary loads, users can earn more by:

  • Watching a short video ad
  • Completing a CPA offer (survey, app install, etc.)
  • Scanning a product barcode for a UGC task

5. Missed Earnings​

If user feels they were not granted a reward for something they expected, they can submit a claim to remedy it:

  1. Tap the receipt icon on the summary header
  2. Edit merchant name (with auto-suggest), date (inline picker), or product details
  3. Add missing products by scanning their barcode
  4. Submit — corrected items turn green and show "Pending Review"

Reward Callback​

Your app needs to implement one closure on BlinkEngageSDK.shared.rewardCallback. While your primary accounting should be based on webhook, these callbacks are provided for the sake of UI continuity immediately after the scan and we advise you reconcile it with the webhook when the webhook is received.

It gets called in four situations:

ContextWho's TalkingWhat To Do
"ScanFinished"SDK asks your appReturn an NSNumber with the base scan reward. A store-specific amount configured by Actual replaces it.
"Promo"SDK tells your appCredit rewardAmount to the user
"Boost"SDK tells your appCredit rewardAmount to the user
"BarcodeCollection"SDK tells your app (iOS only for now; Android will emit it once User Generated Content is supported there)Credit rewardAmount to the user

If you don't set this callback, base scan reward defaults to 0. The SDK still calculates and displays promo/boost/UGC rewards on screen, but your app won't know to credit them.

BlinkEngageSDK.shared.rewardCurrencyName = "points"
BlinkEngageSDK.shared.rewardCurrencyPerDollar = 100.0
BlinkEngageSDK.shared.userPayoutPercentage = 0.5

Reward Examples​

Base Scan Reward​

  • BlinkEngage SDK invokes the rewardCallback to ask host app how much to reward for the current scan
  • Host app responds with a number specifying the base scan reward (if any) in in-app currency
  • This amount is used in 2 ways:
    • It is displayed (along with any other rewards) in the Receipt Summary Screen (RSS) to indicate to the user how many points they received for the current scan
    • It is converted to a dollar amount by dividing by your currencyPerDollar setting and then this dollar amount is sent to your webhook
  • The userPayoutPercentage property has no impact on this reward type
  • Actual can configure an amount for a store that supersedes the host app's amount (see Store-specific base scan reward below)
  • Example:
    • User finishes scan
    • BlinkEngage SDK calls rewardCallback with context=ScanFinished
    • Host app returns 1500
    • BlinkEngage displays 1500 Points at the top of the RSS
    • Webhook sent with rewardValue: 15.00

Store-specific base scan reward​

Actual can configure a base scan reward for a specific store, or a default for all of your stores. A configured amount supersedes the amount your app supplies for receipts from that store, and it can be lower than yours, including zero.

How it works:

  • When a receipt starts processing, the SDK reports your amount to Actual along with the store it identified on the receipt.
  • Actual answers with the amount to apply: its configured amount for that store, or yours when nothing is configured.
  • The Receipt Summary shows the applied amount, and the base_scan RewardUpdate webhook carries it.
  • If Actual's answer doesn't reach the device within a few seconds, the Receipt Summary shows your amount instead. The webhook still carries the amount Actual applied, so credit the shopper from the webhook, not from the screen.
PlatformAvailable fromHow your app learns the applied amount
iOS1.12.0From the base_scan webhook. rewardCallback is asked for your amount before Actual's answer arrives, so it never sees the replacement.
AndroidComing in a future release—
  • Example:
    • Your app returns 1500 for every scan (currencyPerDollar is 100)
    • Actual has configured $0.50 for Store X
    • User scans a Store X receipt
    • BlinkEngage displays 50 Points at the top of the RSS
    • Webhook sent with rewardValue: 0.50
    • A receipt from any other store still earns 1500 Points (rewardValue: 15.00), as long as no default is configured for all stores

Offer Validations​

  • All SKU-based offers have a fixed dollar amount associated with them, which must be paid out entirely to the user
  • Thus, when a user opens the offer wall in your app, BlinkEngage takes the dollar amount of each offer, multiplies by your currencyPerDollar and displays the resulting points value in the card for each offer
  • When a user scans a receipt with qualifying products for a given offer, the user sees the corresponding points value on the RSS in-line with the qualifying product
  • The webhook sent to your backend contains the dollar amount for that offer
  • The userPayoutPercentage has no impact on this reward type
  • Example:
    • An offer for Colgate Toothpaste has a dollar value of $2
    • The user sees this offer on the offer wall as 200 points
    • If the offer scans a qualifying receipt, on the RSS they will see 200 points next to that line item
    • BlinkEngage SDK calls rewardCallback with context=Promo and rewardAmount=200
    • Webhook sent with rewardValue: 2.00 for this offer

Boosts (Watch & Earn / Snap & Earn)​

  • On the RSS we will occasionally present the user with opportunities to “boost” their rewards for that receipt by either watching a rewarded ad (Watch & Earn), or performing a product collection task (Snap & Earn - i.e. snap a picture of the product and scan its barcode)
  • We set the dollar reward amount for these boosts, and we multiply them by both currencyPerDollar AND userPayoutPercentage (if any) to determine the number of points to show alongside the boost CTA
  • The webhook sent to your backend contains the dollar amount for the boost
  • Example:
    • A Watch & Earn boost has a reward value of $1
    • User sees this CTA on the Receipt Summary Screen as 50 points ($1 x 100 x 0.5)
    • The user successfully watches the ad
    • User sees 50 points added to their tally on the Receipt Summary Screen
    • BlinkEngage calls rewardCallback with context=Boost and rewardAmount=50
    • Webhook is sent with rewardValue: 1.00

Error Flows​

A receipt that can't be processed is not a dead end. Instead of ending the scan with an error, the SDK raises a screen that explains what happened and — in every case — offers to send the receipt to a human reviewer. Dismissing that screen returns the shopper to the offer wall with the flow intact, so they can scan again right away.

Only genuine failures still surface as errors: an API timeout or a dropped connection shows an error dialog ("Oops!") and is retried, not reviewed.

Where each check runs​

Receipt checks run in two tiers, and the tier decides whether the receipt is ever registered with our backend at all.

TierWhen it runsWhat it checksRegistered server-side?
On deviceBefore the scan is submitted — no networkUnreadable photo, missing required field, purchase date outside the rewards windowNo
ServerAfter the scan is submitted for processingSubmission limits, receipt age, fraud flags, duplicatesYes

Because the device-tier checks run before submission, a receipt one of them stops is never registered — so it can never come back as a duplicate or pick up a fraud flag either. Receipt age is the one rule checked in both tiers: on device from the date read off the photo, and again by the backend from its own records. Both land on the same screen.

The flows​

What happenedDetectedWhat the shopper seesRetake offered?Where it ends
Photo can't be readOn deviceInvalid-receipt screen, with a checklist of what to checkYesOffer wall
Store, date, or total missingOn deviceA short form asking for what's missingYesContinues to processing → Receipt Summary
Purchase date outside the rewards windowOn device or serverReceipt-too-old screen with a date-correction formNoOffer wall
Receipt matches an earlier submissionServerReceipt Summary with a duplicate banner; rewards suppressed—Receipt Summary
Receipt carries an actionable fraud flagServerReceipt Summary with an invalid banner; rewards suppressed—Receipt Summary
A submission limit was hitServerFrequency-cap screen naming each limit and its value—Offer wall

Two details worth knowing:

  • A missing store name alone never blocks a scan. The store is readable from the photo during review, so asking for it up front would stop the shopper for no benefit. A receipt missing only the store goes straight to processing with the store left blank. A missing date or total — alone, or alongside the store — does show the form.
  • The receipt-too-old screen is the only one with no retake. A newer photo can't change a purchase date that has already been read, so a review request is the only way forward.

Precedence: which screen wins​

More than one thing can be wrong at once. The device checks run in a fixed order, and the first one to fail decides the screen:

  1. Is the photo readable? An unreadable photo says nothing reliable about which fields are present, so this outranks everything else.
  2. Are the required fields there? An absent or unparseable date can't be age-checked, so this comes before the window check.
  3. Is the purchase date inside the rewards window? 14 days by default.
  4. Otherwise the scan is accepted and continues to processing.

Server verdicts have their own fixed order — submission limit, then age, then fraud, then duplicate — so a receipt that is both a duplicate and outside the window reads as outside the window, whichever tier caught it.

What a review request carries​

When a shopper fills in a missing field or disputes a verdict, that value is evidence for a reviewer — it is never merged into the scan. The scan data submitted for processing is always exactly what the scanner read.

The practical consequence: a field that was missing when the receipt was scanned is still missing on the Receipt Summary, even though the shopper just typed it in. Only a reviewer, acting on the review request, can change the value the receipt is ultimately paid out against. Design your own screens the same way — treat a correction as a pending claim, not as corrected data.

One value is validated before it can be submitted at all: a purchase date typed into the form is age-checked as it is entered. A date still outside the rewards window keeps the submit button disabled with an inline message, because no reviewer could make that receipt eligible.

When the scan reward lands​

A scan that ends at a review request earns nothing at scan time. The backend holds the reward and grants it only if a reviewer accepts the receipt, which can be hours or days later.

FlowBase scan reward
Scan accepted normallyPaid immediately
Missing fields, review submittedPaid immediately — this flow continues on to processing
Invalid receipt, receipt too old, submission limit, disputed duplicateDeferred — paid only if a reviewer accepts

Deferred rewards arrive on your server as RewardUpdate webhooks, the same channel as missed earnings. Build for open-ended reward accrual per receipt rather than assuming rewards are final when the shopper leaves the summary.

Retakes keep the same receipt​

Every screen that offers a retake reopens the camera on the same receipt, not a new one. The extra photo is another angle on one purchase, so it keeps the same receipt identifier and the accumulated frames travel with it; the updated scan then re-enters the checks above from the top. A shopper can retake, get a different screen, retake again, and it is all still one receipt as far as rewards, reviews, and webhooks are concerned.

Fraud flags are not automatic rejection​

A fraud flag on its own is not actionable. The backend may flag a receipt for soft or informational reasons — scan velocity, for example — and those receipts process and pay out normally.

A receipt is only treated as invalid when it is flagged and carries one of these five reasons: receipt_counterfeit_text, receipt_merchant_mismatch, user_geo_anomaly, user_identity_anomaly, ip_anomaly. Even then it is not turned away: the Receipt Summary renders with rewards suppressed and the dispute stays open.


Debug Mode​

For development, turn on test ads so you don't serve real ones:

BlinkEngageSDK.shared.debugModeEnabled = true

Turn this off before shipping.


Architecture​

The SDK is organized into self-contained modules:

ModuleWhat It Does
OfferWallScreenOffer display, store filtering, clipping
AdLoadingScreenLoading drawer with progress bar and ad slot
ReceiptSummaryScreenScan results, rewards, boost offers
MissedEarningsScreenReceipt correction UI
StoresScreenStore list and filtering
ProductCaptureScreenBarcode scanning and photo capture for UGC

Patterns used: MVVM for each module, a Coordinator layer (Combine-based) for presentation flow, and a shared service layer for networking, image caching, logging, and Google Ads.

Network handling: built-in NWPathMonitor for connectivity checks, and URLSession configured with waitsForConnectivity = true.