Skip to main content
Encore hosts the Web SDK for you at https://www.encorekit.com/offers. Your app opens that URL in a plain webview, passes its configuration in the query string, and verifies the result from your backend. No package to install, no build step, and nothing to ship in a client release. Use it when installing an SDK is not on the table: a native app with no Encore integration, a kiosk or device shell, a partner surface whose build you do not own, or a pilot you want running this week. Everything else on web should use the Web SDK, which hands the presentation result back to your own code. Three routes serve the identical page, so pick whichever fits your app:
Claiming an offer replaces this page. A webview has no second tab to open, so the handoff to the advertiser is a top-level navigation in the same webview. Your app owns the way back. Read What your host app must get right before you build anything else.

What your host app must get right

The page is built to work in a webview nobody configured, so there is very little to do. These three things are the exceptions, and each one breaks an integration that does not know about it.

Open the page at top level, never in an iframe

Load the URL as the webview’s own document. The advertiser handoff navigates the frame the offer page is running in. Inside an iframe that means the advertiser loads into your iframe rather than taking over the view, so the user ends up on a third-party site rendered in a box inside your app, with your own chrome still wrapped around it and no sensible way out. A top-level document is the only arrangement the handoff is designed for. The symptom is easy to recognize once you know it: the offer itself looks fine, and only after a claim does the user end up stuck on an advertiser page inside a small frame. If you see that, the page is being loaded in a frame.

Claiming replaces the page, so own the way back

This is the one genuinely surprising behavior, and it only surfaces after a successful claim, which is the worst moment to discover it. A locked webview with no back gesture and no surrounding chrome strands the user on the advertiser’s site.
1

Expect the webview to navigate away

After a claim, the webview is showing the advertiser’s site rather than Encore or your app. That is the normal, successful path, not a failure.
2

Give the user a way back

Provide your own close or back control, either in the chrome around the webview or through the webview’s own back stack. The offer page cannot return the user to your app, and there is no confirmation screen to close.
3

Let the navigation through

If your WebViewClient.shouldOverrideUrlLoading or your WKNavigationDelegate.decidePolicyFor restricts navigation to your own domain, the handoff is blocked at the last step. Allow www.encorekit.com and outbound advertiser domains, or pass a blocked URL to the system browser instead of cancelling it.
4

Do not tear the webview down on navigation

A host that closes the webview the moment the URL leaves encorekit.com closes it on the user mid-claim.

This holds for the hosted page only

The hosted page does that work for you. If you ever hand-roll your own page around @encorekit/web-sdk instead, the behavior changes underneath you. The SDK reaches the advertiser through window.open, inside the user’s tap on Claim. That is correct in a browser and a no-op in a webview: window.open returns null in an Android WebView unless the host sets setSupportMultipleWindows(true) and implements WebChromeClient.onCreateWindow, and null in a WKWebView unless the host implements WKUIDelegate.createWebViewWith. There is no second tab to be had, so Claim becomes a dead tap. The hosted page replaces window.open with a stand-in that navigates the current frame, which needs no cooperation from the host at all.
So the dead tap is a consequence of not using the hosted page. A hand-rolled page in a webview must implement onCreateWindow and createWebViewWith itself, or reproduce the same-frame handoff. Using /offers costs you nothing here.
Because the advertiser is reached by a same-frame navigation rather than the SDK’s noopener,noreferrer popup, the page declares a no-referrer policy. Your API key and your end user’s id never travel to the advertiser in a Referer header. The route is also excluded from indexing and from the sitemap.

The URL contract

Every value is trimmed, and a blank value is treated as absent, so ?headline= behaves exactly like omitting headline. Values longer than 256 characters are truncated rather than rejected.

Required

Omit either one and the page renders a diagnostic screen naming what is missing, instead of an offer. Build your URLs against that screen before you ship.

Optional

This page defaults to useCase=rewardUsers, which differs from the Web SDK’s own default of reduceChurn. The SDK’s default suits a save attempt fired automatically as someone leaves. Nothing about this page is automatic: your app opens it at a moment of its own choosing, and that moment is a reward far more often than a cancellation. Pass ?useCase=reduceChurn to get the churn carousel instead. What each value renders is documented in The Claim Flow.
An unrecognized useCase or placement does not fall back to the default. The page refuses to start and names the invalid parameter, because silently presenting the wrong screen to someone who asked for a specific one is the harder failure to track down.

Targeting attributes

Eleven of the SDK’s user attributes are accepted as top-level query parameters. Each carries the same meaning and the same string type it has there: countryCode, postalCode, city, state, language, subscriptionTier, monthsSubscribed, billingCycle, lastPaymentAmount, lastActiveDate, totalSessions Anything else goes in the custom. bag. custom.plan=gold&custom.region=west arrives as custom: { plan: 'gold', region: 'west' }. Custom keys must match ^[a-z0-9][a-z0-9_-]{0,39}$ case-insensitively, and at most 20 are kept. Keys past that cap, and keys that are not plain identifiers, are dropped. Parameters on neither list are ignored and never forwarded. The page reads an allowlist rather than passing the query string through, so a parameter nobody reviewed cannot turn a URL into an open data channel.

Attributes the page refuses

The Web SDK accepts these attributes happily. This page does not take them from a URL: email, firstName, lastName, phoneNumber, dateOfBirth, gender, latitude, longitude A query string is not a private channel. It lands in the webview’s history and in hosting access logs, and direct identifiers and precise location do not belong in either.
Sending a refused attribute is not an error, and the page still opens normally, so watch for this: there is no on-screen indication that the attribute was dropped. It is simply absent from targeting, which surfaces later only as targeting that quietly does less.
If you genuinely need one of these signals, contact Encore about a short-lived signed token rather than putting the raw value in a URL.

Worked URLs

Build the URL on your server, so the key and the id are interpolated somewhere you control, and let the platform encode the values for you:
Build the URL

Webview settings

Nothing here is required to make the page work. Both settings buy you better behavior.

Turning on DOM storage

Android WebView ships with DOM storage turned off. The SDK degrades cleanly, falling back from localStorage to sessionStorage to in-memory, so nothing crashes and nothing looks wrong. What it costs you is that every marker the SDK keeps per user is re-derived on each open: the measurement exposure marker, analytics deduplication markers, the cached entitlement snapshot, and any queued events still waiting to be delivered. The most visible effect is an inflated exposure count in reporting. Two things are unaffected, and worth knowing so you do not go hunting for a problem that is not there. Identity is stable, because userId arrives in the URL on every open rather than out of storage. Experiment cohort is stable too, because it is derived deterministically from that same userId rather than stored and looked up. One line removes the whole issue:

Load it over HTTPS

Point the webview at https://www.encorekit.com directly. Cohort assignment uses WebCrypto, which browsers expose only in a secure context. Serving this page through a plain http:// origin silently drops every user out of measurement.

Verify the entitlement from your server

There is no client bridge, and none is needed. The page and your app never talk to each other. They agree on a userId, and your backend asks Encore what that user holds. Call GET /entitlements/server with the same userId you put in the URL. It is HMAC-signed, so it is a backend call only:
Backend (Node)
Gate real access on verified only. provisional is the immediate unlock a client shows while the brand-side completion is still unverified. Treat it as a courtesy, never as proof.
Two details cause most signature failures, and the endpoint reference covers both: you sign the path without the /encore gateway prefix, and a GET signs a canonical query string rather than a body. Multi-app projects also send X-Platform.
Prefer the push side where you can. The Offer Completed webhook delivers each completion as it verifies, which beats polling for something that may complete days after the user closed the webview.

When the page does not start

Missing or invalid parameters produce a plain screen reading “This link isn’t complete”, followed by the specific problem. Every missing name is listed at once, then every invalid one, so one look is enough: missing apiKey, userId, or missing userId · invalid useCase. That text is aimed at whoever is assembling the URL. Check it first when a webview opens to something other than an offer. A page that starts but presents nothing is a different, normal outcome. It means no offers are eligible for that user right now, and it renders “Nothing available right now”. That is neither an error nor a misconfiguration.

See also