Building First-Party Ads in React Native Without Ad Networks, Tracking, or ATT Prompts


When your mobile app gains traction, the traditional playbook tells you to monetize free-tier users by slapping in Google AdMob, AppLovin, or Unity Ads.

For an early-stage app with ~1,400 Monthly Active Users (MAU), following that playbook is one of the worst mistakes you can make.

Here is what traditional ad networks actually bring to a small app:

  1. Paltry Revenue: At a $2 to $4 CPM, 1,400 MAU scanning a few times a week generates roughly $15 to $30 a month. It doesn’t even cover your cloud infrastructure bills.
  2. The Dreaded ATT Prompt: Apple’s App Tracking Transparency framework requires you to show users a scary permission dialog on launch (“Allow Lotto Check to track you across apps and websites?”). Over 80% of users tap “Ask App not to Track,” and it immediately destroys your onboarding conversion rates.
  3. App Store Review Scrutiny: Third-party ad SDKs bundle opaque tracking libraries that collect device fingerprints, complicating your App Privacy nutrition labels and risking App Store rejections.
  4. Vile UX: Uncontrollable, casino-style interstitial pop-ups and full-screen video ads that degrade user perception of your brand.

Third-party ad networks pay pennies while demanding intrusive tracking prompts that instantly destroy user trust during onboarding.

When scaling Lotto Check, we decided to reject third-party ad networks entirely. Instead, we designed and shipped a first-party, server-driven sponsorship system built directly into Supabase and React Native.

Here is how the architecture works, how we keep it privacy-safe, and why it outperforms traditional mobile ad networks.


1. The Core Philosophy: Flat Sponsorships over CPM Micro-Pennies

Rather than selling programmatic impressions for fractions of a cent, we structured monetization around time-boxed, flat-fee sponsorships tailored to local Canadian lottery and retail brands.

Instead of displaying generic programmatic ads, a local lottery syndicate, news outlet, or hospital foundation lottery (like the BC Children’s Hospital Lottery) can sponsor the app for a fixed period (for example, $500 for 14 days).

This changes the technical equation completely:

  • We don’t need real-time bidding SDKs.
  • We don’t need cross-site user fingerprinting or location tracking.
  • Sponsors receive clean, aggregated reporting (total impressions and click-throughs).
  • Paying Pro subscribers automatically receive a zero-ad experience via their RevenueCat entitlement flag.

Selling flat-rate sponsorships to relevant brands delivers higher net revenue than programmatic networks while keeping the codebase lightweight.


2. The Database Schema: Simple, Robust, and Server-Driven

Everything is driven by Postgres in Supabase. The entire advertising engine requires only two core tables: sponsored_campaigns and sponsored_events.

-- Active campaigns managed directly via Supabase Studio
CREATE TABLE public.sponsored_campaigns (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    sponsor_name TEXT NOT NULL,
    headline TEXT NOT NULL,
    body_text TEXT NOT NULL,
    cta_label TEXT NOT NULL DEFAULT 'Learn More',
    destination_url TEXT NOT NULL,
    image_url TEXT NOT NULL,
    region_whitelist TEXT[], -- e.g. ['ON', 'BC'] or NULL for national
    start_date TIMESTAMPTZ NOT NULL,
    end_date TIMESTAMPTZ NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Aggregated analytics ledger (no PII, no device IDs)
CREATE TABLE public.sponsored_events (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    campaign_id UUID REFERENCES public.sponsored_campaigns(id) ON DELETE CASCADE,
    event_type TEXT NOT NULL CHECK (event_type IN ('impression', 'click', 'dismiss')),
    region TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

Key Security & Privacy Safeguards:

  • No User IDs: sponsored_events contains zero references to user_id, IP addresses, or device IDs. Apple’s privacy guidelines clearly state that first-party aggregated analytics do not trigger ATT tracking rules. User privacy is a competitive advantage rather than an obstacle to revenue (a principle explored in Privacy, Trust, and the New Rules of Modern Marketing).
  • Region-Aware Filtering: Many lottery sponsorships are provincially restricted (e.g., an Ontario-only charity lottery). Campaigns specify a region_whitelist. A secure RPC get_active_sponsored_slot(p_region) delivers only campaigns matching the user’s localized app profile.

Aggregating anonymous impression counts without device IDs keeps your app compliant with Apple privacy rules and avoids intrusive tracking dialogs.


3. The Frontend Architecture: Native Cards in React Native

On the mobile client, the sponsored unit renders as a native card (SponsoredCard.tsx) embedded within our Home feed ScrollView.

Instead of a jarring webview or a generic banner container, the card is styled using our existing design tokens (components/ui/Card.tsx, ThemedText.tsx), complete with a subtle "Sponsored" badge.

// mobile/components/sponsored/SponsoredSlot.tsx
export function SponsoredSlot({ location }: { location: 'home_feed' | 'results' }) {
  const { isPro } = useRevenueCat();
  const { activeCampaign, recordImpression, recordClick } = useSponsoredCampaign(location);

  // 1. Instantly hide for paying Pro members
  if (isPro || !activeCampaign) {
    return null;
  }

  return (
    <Card variant="flat" style={styles.cardContainer}>
      <View style={styles.headerRow}>
        <Badge label="Sponsored" variant="neutral" size="sm" />
        <ThemedText tone="secondary" style={styles.sponsorName}>
          {activeCampaign.sponsor_name}
        </ThemedText>
      </View>
      
      <ThemedText variant="headline" style={styles.headline}>
        {activeCampaign.headline}
      </ThemedText>
      
      <ThemedText tone="secondary" style={styles.body}>
        {activeCampaign.body_text}
      </ThemedText>

      <Button
        title={activeCampaign.cta_label}
        onPress={() => {
          recordClick(activeCampaign.id);
          Linking.openURL(activeCampaign.destination_url);
        }}
        size="md"
        variant="primary"
      />
    </Card>
  );
}

Edge-Case Handling on Mobile:

  1. Impression Throttling: We use React Native’s IntersectionObserver / onLayout event with a 2-second dwell timer. A user quickly flinging past the card in a scroll view does not count as an impression; impressions are only committed when the card is in the viewport long enough to be read.
  2. Offline Fallback: If the network request fails or the user is offline, the slot collapses smoothly to 0-height with no broken image icons or visual placeholders.
  3. The Design Lab for Pre-Flight Review: To test campaign copy, font scaling, and bilingual formatting (Québec French vs. English) before going live, we built an internal SponsoredLab screen (mobile/app/settings/design/sponsored-lab.tsx) that runs in local development builds.

4. The Business & Technical Payoff

By choosing a first-party approach, we achieved three major wins:

  1. Zero App Store Resistance: Our App Privacy nutrition label lists zero tracking identifiers. App Store reviews pass in hours, with zero back-and-forth about ATT dialogs.
  2. 100% Brand Safety: Because campaigns are created in Supabase Studio, we personally approve every image and copy line. There are no spammy scam ads or competing gambling ads polluting our user experience.
  3. Better Economics: A single local sponsor paying a flat $300/month delivers more net revenue than 50,000 programmatic impressions would have generated through AdMob, all with zero SDK bloat.

The Strategic Advantage for Early-Stage Apps

If you run an early-stage utility app with a dedicated, focused audience, stop reaching for AdMob or third-party ad SDKs.

Build a lightweight, first-party sponsorship table in your database, render native UI cards that match your theme, and sell high-value, flat-rate partnerships directly to relevant brands. You preserve your user experience, respect your users’ privacy, and make more money in the process.

TL;DR

Traditional ad networks destroy early-stage mobile UX with invasive tracking prompts and pennies in revenue. By building a first-party sponsorship engine directly in Supabase and React Native, Lotto Check bypassed Apple ATT requirements, eliminated SDK bundle bloat, and delivered higher monetization through flat-fee brand partnerships.