Developer Docs / Web React SDK / Ads
Web React In-App Advertising
Initialize ads in a React app, show GrowthCat-managed banners and interstitials, build custom ad UI, and validate rewarded ads server-side.
Ad formats
Banner and interstitial placements, including rewarded formats.
Exports
GrowthCatAdBanner, GrowthCatAdInterstitial, and useAd.
Init order
Initialize GrowthCat once. Ads config is fetched from the SDK bootstrap.
1. Overview
Ads use the same GrowthCat client as referrals. During initialization, GrowthCat fetches the SDK bootstrap and configures the ad service with the returned ads settings.
2. Install
npm install @growthcat/web react react-dom
# or
yarn add @growthcat/web react react-domInstall the GrowthCat integration skill
The WebSDK repository includes an agent skill that inspects your app, checks the installed SDK types, and adds referrals, attribution, ads, sponsorships, or feedback using your existing architecture.
npx skills add . --skill growthcat-web-integrationRun this from a local GrowthCatSDK-Web checkout. For a remote install, replace the dot with the repository Git URL. Then tell your coding agent which capability and placement you want.
Use $growthcat-web-integration to add a feedback board to my settings page.3. Initialize GrowthCat
Call GrowthCat.initialize() before rendering any ad components.
import { GrowthCat } from "@growthcat/web";
GrowthCat.initialize({
apiKey: process.env.NEXT_PUBLIC_GROWTHCAT_KEY!,
workspace: "live",
logsEnabled: process.env.NODE_ENV !== "production",
measurementMode: "essential",
});Use analytics only after your app establishes consent or another valid legal basis. You can apply consent changes immediately with GrowthCat.setMeasurementMode().
4. Initialize Ads
Ads are initialized by the SDK bootstrap. After GrowthCat.initialize(), the client has access toadsConfig, including whether ads are enabled and the cache TTL.
const client = GrowthCat.shared;
if (client.adsConfig?.adsEnabled) {
GrowthCat.shared.prefetchAdCatalogs(["home_banner", "level_interstitial"]);
}
await GrowthCat.shared.refreshSDKBootstrap();6. Interstitial Ads
import { useState } from "react";
import { GrowthCatAdInterstitial } from "@growthcat/web/react";
export function LevelCompleteScreen() {
const [showAd, setShowAd] = useState(false);
return (
<>
<button onClick={() => setShowAd(true)}>Continue</button>
<GrowthCatAdInterstitial
placementKey="level_complete_interstitial"
appUserId="user_123"
isOpen={showAd}
onDismiss={() => setShowAd(false)}
onReward={(response) => {
// Called only after successful server validation.
grantCoins(response.reward?.amount ?? 0);
setShowAd(false);
}}
onRewardError={(error) => {
// Validation failed: close without granting a reward.
console.error(error.message);
setShowAd(false);
}}
/>
</>
);
}Rewarded interstitials
appUserId for rewarded ads. Only grant rewards from onReward or after validateAdReward confirms the reward server-side.A dismissed ad, elapsed client timer, validation error, or offline state must never grant value. TreatonNoFill as an expected empty state and continue without showing an error.
7. Custom Ad Rendering
Use useAd() when GrowthCat should select and track the ad, but your app owns the visual design. Custom renderers must measure real visibility; the deprecated trackImpression option must not be used for load-time impressions.
import { useEffect, useRef } from "react";
import { useAd } from "@growthcat/web/react";
export function CustomBanner({ placementKey }: { placementKey: string }) {
const ref = useRef<HTMLButtonElement>(null);
const { ad, state, creativeInstanceId, trackEvent } = useAd({ placementKey });
useEffect(() => {
if (!ad || !ref.current) return;
let timer: ReturnType<typeof setTimeout> | undefined;
const observer = new IntersectionObserver(([entry]) => {
clearTimeout(timer);
if (entry.intersectionRatio >= 0.5 && document.visibilityState === "visible") {
timer = setTimeout(() => {
trackEvent("impression", {
visible_fraction: entry.intersectionRatio,
visible_duration_ms: 1000,
});
}, 1000);
}
}, { threshold: 0.5 });
observer.observe(ref.current);
return () => { clearTimeout(timer); observer.disconnect(); };
}, [ad, creativeInstanceId, trackEvent]);
if (state === "loading") return <div style={{ height: 60 }} />;
if (!ad) return null;
const { creative } = ad;
return (
<button
ref={ref}
onClick={() => {
trackEvent("click");
if (creative.destinationUrl) window.open(creative.destinationUrl, "_blank", "noopener,noreferrer");
}}
>
<small>Sponsored</small>
{creative.publicAssetUrl ? <img src={creative.publicAssetUrl} alt="" /> : null}
<strong>{creative.headline}</strong>
<span>{creative.ctaText}</span>
</button>
);
}8. Manual Loading And Events
const ad = await GrowthCat.shared.loadAd({ placementKey: "rewarded_coins" });
if (ad) showWatchAdButton();
const creativeInstanceId = GrowthCat.makeCreativeInstanceId();
GrowthCat.shared.trackAdEvent("impression", ad, {
format: "banner",
placementKey: "home_banner",
appUserId: "user_123",
sessionId,
creativeInstanceId,
metadata: { visible_fraction: 0.75, visible_duration_ms: 1250 },
});
GrowthCat.shared.trackAdEvent("click", ad, {
format: "banner",
placementKey: "home_banner",
appUserId: "user_123",
creativeInstanceId,
});Generate one creativeInstanceId for the presentation and reuse it for its impression and clicks. Available events also include video start, progress, completion, close, and report events.
9. Reward Validation
const response = await GrowthCat.shared.validateAdReward(ad, "user_123", {
sessionId,
viewedSeconds: 30,
completed: true,
});
if (response.rewardValidated) {
grantCoins(response.reward?.amount ?? 0);
}Never grant a reward if rewardValidated is false or the validation call throws.
10. Offline Behavior
- Catalog fetch failures can return a cached entry while the TTL is valid.
- Tracking events are persisted to localStorage and replayed automatically when the browser comes back online.
- Reward validation throws when offline, so no reward should be granted.