Integration Walkthrough
Five steps using the react-native-encore-mock API. Hotspot Havoc wires it exactly
this way.
1. Configuration
Keep your key, the mock switch, and your placement IDs in one place:
export const ENCORE_API_KEY = 'pk_your_api_key_here'; // inert in mock mode
export const USE_MOCK = true;
export const Placements = {
CancellationFlow: 'cancellation_flow',
FeaturePaywall: 'feature_paywall',
};Create the client once and wrap your app in <MockEncoreProvider>, which runs
configure() + registerCallbacks() for you:
import { createMockEncore, MockEncoreProvider } from 'react-native-encore-mock';
import { GAME_OFFERS } from './src/lib/offers';
const Encore = createMockEncore({ offers: GAME_OFFERS });
<MockEncoreProvider client={Encore} apiKey={ENCORE_API_KEY} logLevel="debug">
<App />
</MockEncoreProvider>Why: placement IDs come from the Encore dashboard. Referencing them through a
Placements constant (not bare strings) keeps every call site in sync and turns a
typo into an error.
2. User Identity
Tie tracking to a user as soon as you know who they are:
Encore.identify('user_12345', {
email: 'player@example.com',
subscriptionTier: 'premium',
monthsSubscribed: '6',
});setUserAttributes merges — it never replaces — so you can enrich over time.
On logout, call Encore.reset() to clear identity, entitlements, and attributes.
Why: identifying the user lets Encore sync entitlements across devices and target offers based on attributes like tenure or tier.
3. Presenting Offers
Ask Encore to present an offer at a placement; show() resolves with the result:
import Encore, { isGranted } from './src/lib/encore';
const result = await Encore.placement(Placements.FeaturePaywall).show();
if (isGranted(result)) {
unlockPremium(); // reveal the gated content
}Why: you decide the moment (the cancel tap, the locked feature); Encore
decides whether and what to show — a paid offer or a
brand-sponsored partner trial, handled identically via
isGranted(result).
4. Handling Results
show() resolves with a PlacementResult whose status is your primary
signal ('granted' | 'completed' | 'not_granted' | 'dismissed' | 'no_offers').
isGranted(result) covers both “won” outcomes — granted (a paid purchase) and
completed (a brand-sponsored trial):
try {
const result = await Encore.placement(Placements.CancellationFlow).show();
if (isGranted(result)) {
keepSubscribed();
setMessage(
result.status === 'completed'
? '🎁 A partner is sponsoring your subscription — thanks for staying!'
: '🎉 Thanks for staying! Your discount is applied.',
);
} else {
finalizeCancellation(); // dismissed / no_offers → cancel
}
} catch (error) {
finalizeCancellation(); // never block the user on an SDK error
}The dismissal path also flows through onPassthrough (registered via
useEncoreCallbacks’s onResume) so you can resume the user’s original action and
never trap them.
5. Completing Purchases
Register the callbacks once with useEncoreCallbacks. You supply purchase (your
billing); the hook calls completePurchaseRequest on every path for you:
useEncoreCallbacks(Encore, {
purchase: (productId) => billing.purchase(productId),
onResume: (placementId) => resumeOriginalAction(placementId),
onPurchaseComplete: ({ productId, transactionId }) =>
console.log('[encore] complete', productId, transactionId),
});Why — the #1 gotcha: if completePurchaseRequest(true/false) is skipped
(especially in the failure path), Encore believes a purchase is still in flight and
blocks all future offers. The hook guarantees it runs whether your billing
succeeds or throws — that’s the main reason to use it instead of registering
onPurchaseRequest by hand.
Sponsored partner trials resolve to
status: 'completed'without touchingpurchase— the brand pays. See Sponsored Offers.