milestone_reached (purchase moments: post_purchase). Use it as the name of this surface in your own code and analytics. The feed request itself carries no placement field; the request identifies your integration (clientId) and the end user (userId).
The complete pattern
1
Your milestone logic fires
2
Fetch the feed
When the streak screen is about to show, ask Encore for ranked offers for this user. One call, from your server or your app:An empty
userId must be your stable identifier for this end user: it seeds offer ranking and becomes the user on the transaction created when they tap (see Gotchas). attributes.countryCode and attributes.language are required; attributes.platform is optional for API-platform apps. The full field contract, including X-Platform for multi-app projects, is in the feed reference.The response is a ranked array (full response contract: feed reference); for a single reward card you render offers[0]:offers array means no eligible offer for this user right now: show your streak screen without the reward card and move on (never block your own moment on it).3
Render the offer in your UI
Map the response fields onto your own card, inside your streak screen:
Your button, your copy, your layout: Encore ships no UI here. If you render more than one offer, cycle through the array at your own pace and repeat steps 4 and 5 per offer.
4
Confirm the impression when the card renders
The feed call is not the impression. The moment the offer card actually appears on screen, confirm it with that offer’s
impressionUid:deliveredTimestamp is required: Unix seconds at render time. The server responds 202 { "success": true } (full contract).The discipline is render-then-confirm, never confirm without render:- Fire one
deliveredcall per offer, at the moment it becomes visible: not when you fetch the feed, and never for offers the user didn’t see. - If a call clearly failed (network error, non-2xx), retry it. The server accepts repeated confirmations but does not guarantee deduplication; treat one successful
deliveredper rendered offer as the contract. - These confirmations are what record an offer as actually seen. Skip them and your rendered offers are indistinguishable from ones the user never saw.
5
The user taps: route to clickUrl
Wire your button to open the offer’s
clickUrl (in-app browser, Custom Tab, SFSafariViewController, or a new web tab). That link is the claim: Encore records an attributed transaction, bound to your app, the offer, the creative, and the userId from step 2, and 302-redirects the user to the advertiser.Because clickUrl is a plain branded short link, you can also let the user share the offer with a friend: drop it into a share sheet, and whoever opens it gets the same attributed redirect.A claim is not a conversion. The user tapping through earns nothing yet; the activation is verified server-side on the brand’s side, and only a verified completion counts (and pays). If your app should react when that happens, subscribe to the offer_completed webhook.Gotchas
- Keep
userIdstable. Ranking, click attribution, and completion verification all key on it. A per-session or rotating id fragments the user across transactions and breaks attribution. - Feed, not catalog. Per-offer
impressionUids anddeliveredconfirmation exist only on/offers/feed; rewarded-action moments always use the feed, so their impressions are fully accounted for. - Don’t cache offers across sessions. Each
clickUrlis a short-lived tokenized link that expires server-side, and ranking is per-request. Fetch a fresh feed each time the moment fires, and only once per rendered moment, since feed retries aren’t deduplicated (each call creates new links and analytics rows). - Don’t build your own eligibility layer. Entitlement, geo, and ranking re-run server-side on every feed call; an empty
offersarray is the server telling you there’s nothing to show right now. Render your moment without the card and try again at the next moment. - Geo is required.
attributes.countryCodemust be two uppercase letters (ISO 3166-1 alpha-2) or the request is a 400. Offers not redeemable in that country are never returned.
Verifying your integration
What you can observe end-to-end today:- Feed responses. A
200with a non-emptyoffersarray (with apk_live_*key) means auth, eligibility, and targeting are wired. An empty array is a valid response, not an error: retest with a user/geo you know has eligible offers. - Delivered confirmations. Each render-time confirmation returns
202 { "success": true }. A400means a malformedimpressionUid(it must be the UUID from the feed response) or a missingdeliveredTimestamp. - Attributed transactions. Tapping
clickUrlshould land the user on the advertiser page via the 302. If you’ve configured the offer_completed webhook, a verified completion later POSTs to your endpoint with the same user binding.
pk_test_* keys accept delivered calls and clicks but don’t track them; use pk_live_* for anything you want to count.
Related
POST /offers/feedreference: full request/response contract, auth, status codes, image fields.POST /offers/impressions/{impressionUid}/deliveredreference: the impression-confirmation contract.- API Reference overview: the shared flow, the impression and claim models, and the selection-mode decision table.
- Reward Users: the use case this guide implements (moment, mechanic, economics).