Skip to main content
Presents targeted offers to users in a modal overlay. Returns a promise that resolves when the modal closes with the presentation result.

Signature

function presentOffer(options?: PresentationOptions): Promise<PresentationResult>

Parameters

options
PresentationOptions
Optional callbacks and configuration
interface PresentationOptions {
  onGranted?: (entitlement: EntitlementType) => void
  onNotGranted?: (reason: NotGrantedReason) => void
  onError?: (error: EncoreError) => void
}

Return Value

Type: Promise<PresentationResult>
interface PresentationResult {
  granted: boolean
  entitlement?: EntitlementType
  reason?: NotGrantedReason
}
granted
boolean
Whether an entitlement was granted
entitlement
EntitlementType
The entitlement type that was granted (only present if granted is true)
reason
NotGrantedReason
Reason why entitlement wasn’t granted (only present if granted is false)Possible values:
  • 'userClosedModal' - User clicked X or pressed ESC
  • 'userClickedOutside' - User clicked modal backdrop
  • 'userDeclinedLastOffer' - User clicked “No Thanks” on last offer
  • 'noOffersAvailable' - Server returned no offers
  • { type: 'error', error: EncoreError } - System error occurred

Examples

Basic Usage

import Encore from '@encore/web-sdk';

const result = await Encore.presentOffer();

if (result.granted) {
  console.log('Access granted:', result.entitlement);
  unlockFeature();
} else {
  console.log('Not granted:', result.reason);
}

With Callbacks

await Encore.presentOffer({
  onGranted: (entitlement) => {
    console.log('User granted:', entitlement);
    unlockFeature();
    trackEvent('offer_accepted');
  },
  onNotGranted: (reason) => {
    console.log('User declined:', reason);
    if (reason === 'noOffersAvailable') {
      showAlternativeFlow();
    }
    trackEvent('offer_declined', { reason });
  },
  onError: (error) => {
    console.error('Error:', error);
    showErrorMessage();
  }
});

Handling All Results

const result = await Encore.presentOffer();

if (result.granted) {
  // User claimed an offer
  console.log('Entitlement type:', result.entitlement.type);
  unlockPremiumFeature();
} else {
  // User didn't claim
  switch (result.reason) {
    case 'userClosedModal':
      console.log('User closed the modal');
      break;
    case 'userDeclinedLastOffer':
      console.log('User declined all offers');
      showPricingPage();
      break;
    case 'noOffersAvailable':
      console.log('No offers available');
      showAlternativeFlow();
      break;
    default:
      if (typeof result.reason === 'object' && result.reason.type === 'error') {
        console.error('Error:', result.reason.error);
      }
  }
}

Framework Integration

  • React
  • Vue
  • Angular
import { useState } from 'react';
import Encore from '@encore/web-sdk';

function UnlockButton() {
  const [loading, setLoading] = useState(false);
  
  const handleClick = async () => {
    setLoading(true);
    try {
      const result = await Encore.presentOffer();
      if (result.granted) {
        // Feature unlocked - UI updates automatically via event listeners
      }
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Loading...' : 'Unlock Premium'}
    </button>
  );
}

Common Use Cases

Cancellation Flow

async function handleCancelSubscription() {
  const confirmed = await showCancelConfirmation();
  if (!confirmed) return;
  
  const result = await Encore.presentOffer();
  
  if (result.granted) {
    cancelCancellationFlow();
    showMessage('Your subscription continues with a special offer!');
  } else if (result.reason !== 'noOffersAvailable') {
    await cancelSubscription();
    showMessage('Your subscription has been cancelled.');
  }
}

Feature Paywall

async function showPremiumFeature() {
  if (Encore.isActive({ type: 'freeTrial' })) {
    renderPremiumContent();
    return;
  }
  
  const result = await Encore.presentOffer();
  
  if (result.granted) {
    renderPremiumContent();
  } else {
    redirectToPricingPage();
  }
}

Onboarding Enhancement

async function completeOnboarding() {
  await finishOnboardingSteps();
  
  const result = await Encore.presentOffer();
  
  if (result.granted) {
    showPremiumOnboarding();
  } else {
    showStandardOnboarding();
  }
}

Presentation Flow

1

SDK Fetches Offers

The SDK calls the Encore API to fetch available offers based on user ID, attributes, location, and targeting rules.
2

Modal Displays

A responsive modal overlay appears with offer details, instructions, and call-to-action buttons.
3

User Interacts

User can claim an offer (opens advertiser URL), decline (shows next offer), or close the modal.
4

Provisional Grant

When user claims an offer, the SDK sends a provisional grant signal and refreshes entitlements automatically.
5

Modal Closes

Success screen is shown, then the modal closes and the promise resolves.
The SDK automatically refreshes entitlements after an offer is claimed. You don’t need to manually call refreshEntitlements().

Best Practices

1. Check Before Presenting

// Good
if (!Encore.isActive({ type: 'freeTrial' })) {
  await Encore.presentOffer();
}

2. Handle All Outcomes

// Good
const result = await Encore.presentOffer();
if (result.granted) {
  // Success
} else if (result.reason === 'noOffersAvailable') {
  // No offers
} else {
  // User declined
}

3. Show Loading States

// Good
setLoading(true);
try {
  await Encore.presentOffer();
} finally {
  setLoading(false);
}

4. Track Analytics

const result = await Encore.presentOffer();
analytics.track(result.granted ? 'offer_accepted' : 'offer_declined', {
  entitlementType: result.entitlement?.type,
  reason: result.reason
});

Next Steps