# Indie Exchange Unity SDK 0.5.3

[Download Unity package](https://indie-exchange.com/downloads/IndieExchange-0.5.3.unitypackage) | [Unity SDK on GitHub](https://github.com/samdogsoft/indie-exchange-unity)

## Setup

1. On the website, add your games in **My games** and copy the publisher game's **Game ID**.
2. In **Ads**, create an ad for a game and choose its format. Import store artwork or upload a checked creative. Activate the ad when ready.
3. In **My games → Advertising settings**, set the overall Indie Exchange share for each format, the public/personal split, and the personal ads and weights. Choose the banner size and position there too. New games start at 0% until configured.
4. Import `IndieExchange-0.5.3.unitypackage`. Open **Tools → Indie Exchange → Setup**, create settings, paste **Game ID**, and leave **Test Mode** on while checking the integration.
5. Initialize once after your consent flow permits requests. Keep your existing ad provider ready independently.

```csharp
using IndieExchange;

// Once, after consent. Uses the Game ID / Test Mode settings asset.
IndieAds.Initialize();
// Alternatively, configure by code:
// IndieAds.Initialize("YOUR_GAME_ID", testMode: false);

// At a natural interstitial break (await before resuming gameplay):
if (!await IndieAds.TryShowInterstitialAsync())
    ShowMyNormalInterstitial();

// When your game wants a banner:
if (!IndieAds.TryShowBanner())
    ShowMyNormalBanner();

// When leaving that screen:
IndieAds.HideBanner();
```

Call on Unity's main thread. Do not display both providers' banners together. Hide your normal banner before displaying an exchange banner. Hide banners before fullscreen ads and restore your chosen provider afterward. The host owns its normal provider's loading, readiness and failure handling.

`true` from the interstitial task completes after the exchange ad closes. `false` completes immediately, without a network request from the show call. Repeated calls while an interstitial is open share the same task. Your normal provider may itself have no ad; this package cannot guarantee its fill.

## Pausing during full-screen ads

Interstitial and rewarded ads automatically pause scaled game time and game audio. This includes normal physics, animations, and `WaitForSeconds` coroutines. The ad video, its audio, buttons, and reward timer continue using unscaled time. Closing, destroying the overlay, a playback error, or SDK shutdown restores the previous time scale and audio pause state before close/reward callbacks and the awaited show task complete. A game already paused remains paused. Banners and unavailable ads do not pause anything.

Custom code that uses real time or unscaled animation is still running, as are Unity `Update` methods. If your game deliberately uses those for gameplay, suspend that work while `IndieAds.IsShowingInterstitial` is true (this existing property covers both interstitial and rewarded overlays). Avoid changing the game's global time scale or audio pause setting while an ad owns the pause. This behavior applies to Indie Exchange ads; keep the existing pause handling for your other ad provider.

## Website-controlled launch settings

Live mode fetches a signed-installation configuration **once per game per app launch**, then preloads eligible creatives in the background. Calls return false while configuration or artwork is unavailable. A failed or invalid configuration disables exchange ads for that launch; it is not retried in the background or on scene changes. Reinitializing the same game in the same process reuses the launch result, including failure. Website changes apply on the next launch.

The server stores an immutable configuration snapshot, bound to the registered game and installation key. Ad requests must include the launch session and public/personal category. The server validates the category and uses its stored banner size, selected personal ads and weights. Configuration sessions expire after 24 hours; an unusually long-running session then falls back until the next app launch. Campaign pauses, game suspension, credit limits and security checks still apply immediately to new requests/claims.

For each format:
- **Overall share:** the fraction of eligible opportunities allocated to Indie Exchange.
- **Public share:** the fraction of those selected opportunities allocated to other developers' games. The remainder is personal.
- **Personal weights:** relative chances among selected personal ads that are currently eligible. Weight 2 receives twice the chance of weight 1; finite samples vary.

Example: 20% overall and 50% public means 10% public, 10% personal, and 80% your normal ad code. Persistent integer counters spread the overall and category decisions across opportunities. At an unchanged 5%, exactly 5 of every 100 counted opportunities are selected. Within selected opportunities, each category has the same counter guarantee. Actual displays may be lower because of missing creatives, targeting, caps or connectivity. When the server option **Show a personal ad when no public ad is ready** is enabled (the default), an unavailable public selection uses a ready linked personal ad within that same opportunity. This also works at 100% public share. Otherwise an unavailable selection returns false. Counters still schedule the configured split; actual personal displays can exceed that split. There is no catch-up debt or additional percentage roll. Reinstalls or cleared data reset counters.

Normal calls always use server percentages. Explicit no-fill recovery is available with `bypassPercentage: true`, as described below; live 0% remains disabled. Named placement overloads remain for source compatibility, but live names are the internal `interstitial`, `banner`, and `rewarded` formats. New integrations should use the no-argument calls above. Custom placement names from 0.2 must be removed.

## Test Mode and live delivery

In Unity Editor Play Mode, Test Mode shows built-in sample interstitials, banners, and a three-second rewarded placeholder, with or without a Game ID. It works offline and makes no server or media requests. On a device with a Game ID, Test Mode downloads your server settings and real active creatives, including in USB-installed and non-development builds. Preview uses 100% overall share and the configured public/personal categories and personal weights. It prefers a ready personal preview if available, then a ready public preview, so testing is predictable; this preview behavior does not change live allocation. It ignores balances and budgets, but still requires eligible active ads and matching artwork. TEST labels remain visible; preview clicks do not open the store. The server issues no credit tickets and records no production serves, impressions, clicks or statistics. With no Game ID, Test Mode retains an offline sample card. Turn Test Mode off before publishing your game.

Live release builds always use `https://indie-exchange.com`. No service URL, percentage, cache, or timeout controls appear in the normal Inspector. Advanced development-only endpoint overrides and cache/timeout values remain accessible through code; production ignores endpoint overrides. Live configuration controls ad allocation, not those internal tuning values.

Live delivery needs only the Game ID. The SDK generates an installation signing key automatically; no certificate, Google project, Play install, or App Attest setup is required. No AdMob or other ad-provider dependency is installed. Existing native attestation/build-hook assets are inert upgrade placeholders so importing this version also disables old hooks. A signature establishes continuity of an installation key, not proof of a genuine device or app ownership. Eligible credits settle automatically, including for new publishers. Only integrity warnings or unusual traffic/credit volume are held for review. An unavailable country lookup uses the fallback country rate. Personal-only opportunities move no credits. A personal backup supplied for an empty public opportunity can earn no-fill credits, subject to the same review controls. See **Security-Setup.md**.

## Public no-fill credits (0.5.3)

With an updated server, an eligible public opportunity can earn credits when no public ad is available, whether personal fallback is enabled or disabled. The server tries linked personal ads when that option is enabled. A supplied personal backup earns its country and media rate after a live impression (after completion for rewarded video), without charging the personal campaign.

If the server cannot supply a creative, it can return a signed, one-use no-fill receipt. The SDK claims it only when a normal `TryShow` call selects a public opportunity and no public creative is ready. The call still returns false immediately when no ad is shown, so your existing provider can run. No network wait is added to showing an ad. The no-creative rate uses the lowest enabled media rate for that format, the country weight, and publisher earn ratio. It does not claim that a video was watched or create an impression. A rewarded no-fill never grants a player reward.

Preloading alone, personal-only allocations, test/preview traffic, network failures without a valid receipt, and `bypassPercentage: true` recovery calls do not earn these credits. A receipt expires and can be consumed once. Installation signatures, cooldowns, daily limits, suspicious-traffic review, and settlement checks still apply. See **Credits** on the website for pending amounts and review reasons; pending credits are not available balance.

Publish the updated server and update both games to SDK 0.5.3. Older SDKs still work, but cannot claim empty opportunities without a creative. Existing historical zero-credit impressions are not automatically reclassified because they did not record a verified public no-fill.

## Formats and readiness

- Image/video interstitials and sized image banners are supported.
- Banner artwork must match the website-selected 320×50, 320×100, 300×250 or 728×90 proportions; server settings also select top/bottom position.
- Rewarded videos are supported. Grant the player reward only in `onRewardEarned`, never from the returned display boolean.
- `IndieAds.ConfigurationError` explains a failed launch; `IndieAds.LastLoadError` explains the latest creative load failure.
- `IndieAds.IsConfigurationReady` reports whether a valid launch policy is available.
- `IndieAds.IsReady(IndieAdFormat.Interstitial)` indicates cached availability in at least one category. It does not guarantee the next opportunity's selected category is ready.
- `IndieAds.Preload(IndieAdFormat.Interstitial)` requests background preloading without counting an opportunity. Automatic preloading normally makes this unnecessary.
- `IndieAds.GetStatistics(IndieAdFormat.Interstitial)` reports counted opportunities, selections, actual displays and unavailable selections. Diagnostics never award credits.

Images/video are cached only after validation and must be ready before display. Live caches have up to six slots (public/personal × interstitial/banner/rewarded), with bounded downloads and backoff. Impression tickets expire after five minutes; cache lifetime is capped by ticket expiry. Signed impression claims retain the existing review, anti-replay and credit-accounting checks.

## Package contents and updating

The main `.unitypackage` contains Runtime, one Editor folder with setup, and documentation. Tests stay in the source package. The optional `IndieExchange-Examples-0.5.3.unitypackage` adds the demo and its scene generator; it is not required for a game build. Import it after the main package to use **Tools → Indie Exchange → Create sample scene**.

For an existing 0.2 or 0.3 installation, import 0.4 over the same `Assets/IndieExchange` folder, enter the new **Game ID** (the game's UUID from My games), remove placement arguments in your calls, and save website settings. Existing local SDK keys and percentages no longer control live delivery. Unity imports do not remove old files: if you previously imported Tests or the sample, remove only those optional SDK folders after checking that your own code does not reference them.

Backend and SDK must be upgraded together. The backend adds configuration/session tables without deleting games, ads, balances or history. Old requests without a launch session fail closed. New-game default percentages are zero, so configure the website before expecting live ads.

Validate on target phones before release, including video, orientation, store links, consent, scenes and service outages. Automated Editor tests do not replace on-device playback testing.


## Live request cooldown (0.4.3)

The server allows one live ad-selection attempt every 30 seconds per game installation and ad format. Banner and interstitial loads have independent deadlines; public/personal categories share a deadline. A no-fill result still consumes the interval. Reinitializing, changing campaigns, or restarting IIS does not reset it. The deadline uses PostgreSQL time and an atomic update, including across server replicas. Requests rejected during cooldown create no ad serves, impression tickets or credits.

HTTP 429 includes Retry-After. The SDK pauses both category loads for that format and retries automatically; cached ads and your existing provider remain available. Initial public/personal preloading can take more than one interval. Test previews are exempt from this cooldown because they never affect production accounting; their existing abuse limits still apply.

The operator may set SdkSecurity:AdRequestCooldownSeconds (default 30, enforced range 1–3600) in server configuration. No developer setup changes are required. This identifies a persisted installation key, not an immutable physical device: reinstalling/clearing data can create another key. Network and publisher limits, one-use tickets, and credit review still apply. Client-reported opportunity counters are diagnostics, not credited impressions.


## Recovery after another provider has no ad (0.4.3)

Keep your normal provider preloaded independently. At an ad break, try Indie Exchange normally, then your existing provider. Only if that provider has no ready ad or reports a failure before opening, call `await IndieAds.TryShowInterstitialAsync(bypassPercentage: true)`. For a banner no-fill, use `IndieAds.TryShowBanner(bypassPercentage: true)` when artwork is ready. Calls use the cache and return immediately if unavailable; they never start a request or bypass server load limits. Do not recover after a paid ad has already opened or closed, and guard repeated callbacks so one break cannot show two ads.

Recovery bypasses the normal overall/category percentage roll and uses an already-cached permitted personal/public category. It records a separate fallback display without adding another opportunity or normal selection. A live format set to 0% stays disabled. Server targeting, allowed categories, creative expiry, credit review, and the shared 30-second cooldown continue to apply. Normal percentage selection is unchanged; recovery is additional fill when the other provider has no ad.

Public and personal background loads now take turns. A throttled category retains priority until it can use the next server window. No-fill yields to the other category, preventing repeated public no-fill from starving your own ads.

For banners, keep the existing provider available while Indie artwork loads, then make at most one normal percentage decision per banner opportunity. A no-fill recovery must not loop normal percentage decisions. Use `IndieAds.IsShowingBanner` to detect that an Indie fullscreen ad removed the banner and restore your chosen banner afterward. Initialize and call the SDK on Unity's main thread; marshal other providers' callbacks back to that thread.


## Personal fallback for unavailable public ads (0.4.4)

Each format has a website checkbox: **Show a personal ad when no public ad is ready**, enabled by default. On a selected public opportunity, the SDK uses public inventory first, then an eligible cached personal ad, then returns false for the normal provider. It does not request or wait for an ad during the show call. The option applies to banners and interstitials, including 100% public share; choose the allowed personal ads below the checkbox. Disabling it keeps the scheduled categories separate.

This is part of the normal overall share: one opportunity, one selection and at most one display. It does not increment the separate extra-provider-recovery counter. Actual personal displays may exceed their scheduled share. A 0% overall share stays off; targeting, caps, tickets and the shared cooldown still apply. If no eligible creative is cached yet, normal provider fallback still runs.

Publish the updated MVC server and rebuild the game with 0.4.4 together. Existing stored settings default to enabled without a database migration. The choice is downloaded at game launch. An older server that omits the flag keeps the old strict split in the new SDK until upgraded.


## Rewarded video (0.5.0)

In your ad group, check **Also use as rewarded** under an existing interstitial video, or upload a rewarded video. This reuses the same file, shared settings, and content approval. In the publishing game's advertising settings, configure Rewarded share, public/personal split, and linked personal groups. All settings are downloaded at launch. Public content still needs review; personal content does not.

Offer the player a clear choice such as **Watch an ad for 20 coins**, then call:

```csharp
// Called only after the player accepts your reward offer, on Unity's main thread.
bool displayed = await IndieAds.TryShowRewardedAsync(
    onRewardEarned: () => GivePlayerCoins(20));
if (!displayed)
    ShowYourNormalRewardedAd(); // Its own earned callback grants the same reward.
```

The callback fires once after full playback and closure. `true` means an Indie ad displayed and closed, INCLUDING early exit; it does not mean a reward was earned. Close/back offers **Leave without your reward?**. Cancelling, playback failure, or shutting down before completion grants no reward. Buffering, seeking, and background time do not count toward completion. A stalled player closes after 15 foreground seconds without progress. A rewarded callback supplied by a repeated show call while a fullscreen ad is already open is ignored; guard the host's reward button while awaiting the first call.

Use `IndieAds.IsReady(IndieAdFormat.Rewarded)` to indicate readiness. Normal show calls use server percentages; readiness does not guarantee that the next category is ready. Only after another provider fails BEFORE displaying, you can use `TryShowRewardedAsync(() => GivePlayerCoins(20), bypassPercentage: true)` once. A live 0% setting stays off. No AdMob dependency or extra setup is added.

Editor Test Mode simulates completion with a clearly marked three-second card. Device Test Mode previews a configured video. Both exercise the reward callback, so test with a test player balance. Neither sends production tracking or earns exchange credits.

For live rewarded ads, the signed impression marks the start and earns no exchange credits. A separate signed completion must arrive after the server-checked video duration and before ticket expiry; then existing approval, wallet, limits, and review rules apply exactly once. The local game reward does not wait for accounting/network responses. This is not server verification of your game's currency; a modified client can forge playback, so installation signing, rate limits, and review remain necessary.

Publish the updated Web project and import/rebuild with 0.5.0 before enabling Rewarded traffic. Existing games receive rewarded share 0 unless their package declares support, leaving their other formats available. The initial rewarded video media rate is 3× and can be changed or disabled by an administrator. The one-time schema upgrade enables the formerly unavailable rewarded-video rate; subsequent starts preserve operator changes. Existing game percentages stay unchanged.


### Bundled test artwork

Offline samples use branded PNG artwork for both full-screen orientations and each supported banner size. Full-screen samples switch artwork when the screen rotates. The rewarded sample retains its three-second simulated watch time. Device previews with a Game ID still show eligible server creatives.
