> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Integration Patterns

> How to structure Encore offer calls in web app code: branch on the awaited ShowResult ('claimed' | 'dismissed' | 'unavailable'), drive loading UI with onLoadingStateChange, and wrap show() for cross-cutting telemetry.

`Encore.placement(id).show()` (and the inline `Encore.show()`) resolves when the offer flow
ends. On web there are no global purchase handlers to register: when a user claims an
offer, the SDK opens the advertiser's URL directly (at claim time in immediate mode, at
[`redeem()`](/publishers/web/sdk-reference/redeem) time in deferred mode). Your integration
is just: await the call, branch on the result.

For the cross-platform decision tree, see [Integration Patterns](/concepts/integration-patterns).

## Branch on the result

`show()` resolves a bare [`ShowResult`](/publishers/web/sdk-reference/show) and never
rejects:

```javascript theme={null}
import Encore from '@encorekit/web-sdk';

async function onCancelTapped() {
  const result = await Encore.placement('cancel_flow').show();

  switch (result.status) {
    case 'claimed':
      // Deferred: run your conversion, then Encore.redeem().
      // Immediate: advertiser tab already open; render your own confirmation if wanted.
      keepSubscriptionActive();
      break;
    case 'dismissed':
      // User closed/declined (result.reason says which), or an internal error
      // (result.reason.type === 'error'). Proceed with the original flow.
      await proceedWithCancellation();
      break;
    case 'unavailable':
      // No eligible offers: show your fallback.
      await proceedWithCancellation();
      break;
  }

  analytics.track('cancel_flow_resolved'); // shared tail runs once
}
```

## Loading state

The [placement builder](/publishers/web/sdk-reference/placement) exposes a loading callback
for spinners and button states:

```javascript theme={null}
const result = await Encore.placement('cancel_flow')
  .onLoadingStateChange((isLoading) => setSpinner(isLoading))
  .show();
```

## Cross-cutting telemetry

For consistent analytics across many placements, wrap the placement call in a small helper
instead of duplicating tracking at each call site:

```javascript theme={null}
async function showOffer(placementId, placementOptions) {
  analytics.track('offer_surface_opened', { placementId });
  const result = await Encore.placement(placementId, placementOptions).show();
  analytics.track('offer_surface_resolved', { placementId, status: result.status });
  return result;
}
```

## Rules of thumb

* **Always handle all three statuses.** `unavailable` is a normal outcome (no eligible
  offers), not an error; have a fallback path.
* **Never gate access on the client result.** `claimed` is not a conversion; verify
  entitlements server-side, by user, via Encore's server-side entitlements API.
* **Call from user actions.** Present offers in response to a real moment (cancel tap,
  checkout success), and in deferred mode call `redeem()` from a click handler so the
  advertiser tab isn't popup-blocked.

## See also

* [Concepts: Integration Patterns](/concepts/integration-patterns): cross-platform decision tree
* [Redemption Modes](/publishers/web/concepts/redemption-modes): deferred vs immediate walkthrough
* [`show()`](/publishers/web/sdk-reference/show) · [`redeem()`](/publishers/web/sdk-reference/redeem) · [`placement()`](/publishers/web/sdk-reference/placement)
