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:
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.
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
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.
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
Webview settings
Nothing here is required to make the page work. Both settings buy you better behavior.Turning on DOM storage
AndroidWebView 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 athttps://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 auserId, 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)
/encore gateway prefix, and a GET signs a
canonical query string rather than a body. Multi-app projects also send X-Platform.
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
- The Claim Flow: what a claim does, and what the claimed result carries
- GET /entitlements/server: the full endpoint contract for the call shown here
- Offer Completed Webhook: the push counterpart
- Web SDK Quickstart: the full integration, for when you can install a package