> ## 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.

# Rewarded Actions

> Reward a milestone, streak, or purchase moment with a brand-funded offer rendered in your own UI, over REST.

Reward the user's accomplishment with a brand-funded offer, in your own UI, at the moment your own code already detects. Rewarded actions is the Encore API's most common integration, so this guide is the group's **one end-to-end walkthrough of the whole flow** ([overview](/publishers/offers-api/overview#the-flow)); the guides cover only what differs per selection mode. It walks the full [Reward Users](/publishers/use-cases/post-action-reward) use case with a streak example: a news app whose reader just finished her **7th consecutive day**. The app shows its own "you're all caught up: 7-day streak" screen, and inside that screen renders one brand offer card with its own button. Encore picks the offer; everything the user sees is yours.

Use this pattern when *your* code owns the moment (a streak, a milestone, a completed purchase) and *you* render the surface.

A good placement label for milestone and streak moments is **`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

```mermaid theme={null}
sequenceDiagram
    participant User as End user
    participant You as Your app / UI
    participant Encore

    User->>You: hits the milestone
    You->>Encore: POST /offers/feed
    Encore-->>You: { offers: [ { impressionUid, clickUrl, ... } ] }
    You->>User: render the offer card in your own UI
    You->>Encore: POST /offers/impressions/{impressionUid}/delivered
    User->>You: taps the offer
    You->>User: open clickUrl (link.encorekit.com short link)
    Note over Encore: 302 → advertiser + click/claim registered
```

<Steps>
  <Step title="Your milestone logic fires">
    ```javascript theme={null}
    // your streak logic, e.g. the user's 7th consecutive daily open
    showStreakScreen(user); // your own "7-day streak!" UI
    ```

    Nothing Encore-specific happens here. The trigger is code you were writing anyway.
  </Step>

  <Step title="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:

    ```bash theme={null}
    curl --request POST 'https://api.encorekit.com/encore/publisher/sdk/v1/offers/feed' \
      --header 'X-API-Key: YOUR_API_KEY' \
      --header 'Content-Type: application/json' \
      --data '{
        "clientId": "newsreader-app",
        "userId": "user-8b3f7c10",
        "limit": 3,
        "attributes": {
          "countryCode": "US",
          "language": "en",
          "platform": "ios"
        }
      }'
    ```

    `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](#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](/publishers/offers-api/reference/feed#request).

    The response is a ranked array (full response contract: [feed reference](/publishers/offers-api/reference/feed#response)); for a single reward card you render `offers[0]`:

    ```json theme={null}
    {
      "success": true,
      "offers": [
        {
          "impressionUid": "3f2a8c1e-5b7d-4e2a-9c6f-1d8e4b7a2c5f",
          "clickUrl": "https://link.encorekit.com/Kq8xW2mR",
          "title": "3 Months of Music, Free",
          "description": "Celebrate your streak with a free trial",
          "imageUrl": "https://cdn.encorekit.com/creatives/acme-music-banner.png",
          "additionalImages": [],
          "advertiserName": "Acme Music",
          "advertiserLogo": "https://cdn.encorekit.com/orgs/acme-music-logo.png",
          "perk": "3 months free",
          "badgeLabel": "free_trial"
        }
      ]
    }
    ```

    An empty `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).
  </Step>

  <Step title="Render the offer in your UI">
    Map the response fields onto your own card, inside your streak screen:

    | Response field                      | Card element                                                                                             |
    | ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
    | `advertiserLogo` + `advertiserName` | The brand mark and name ("from Acme Music")                                                              |
    | `title`                             | The card headline                                                                                        |
    | `perk`                              | The deal line ("3 months free"); may be `null`                                                           |
    | `badgeLabel`                        | Optional badge: map `"free_trial"` / `"discount"` to your own display text; `null` means no badge        |
    | `imageUrl`                          | Hero image (wide \~2.4:1 banner; see [image fields](/publishers/offers-api/reference/feed#image-fields)) |
    | Your own button                     | The CTA, wired to open `clickUrl` on tap                                                                 |

    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.
  </Step>

  <Step title="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`:

    ```bash theme={null}
    curl --request POST \
      'https://api.encorekit.com/encore/publisher/sdk/v1/offers/impressions/3f2a8c1e-5b7d-4e2a-9c6f-1d8e4b7a2c5f/delivered' \
      --header 'X-API-Key: YOUR_API_KEY' \
      --header 'Content-Type: application/json' \
      --data "{ \"deliveredTimestamp\": $(date +%s) }"
    ```

    `deliveredTimestamp` is required: Unix **seconds** at render time. The server responds `202 { "success": true }` ([full contract](/publishers/offers-api/reference/delivered)).

    The discipline is **render-then-confirm, never confirm without render**:

    * Fire one `delivered` call 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 `delivered` per 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.
  </Step>

  <Step title="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](/publishers/offers-api/guides/receive-completion-events).
  </Step>
</Steps>

## Gotchas

* **Keep `userId` stable.** 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 `impressionUid`s and `delivered` confirmation 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 `clickUrl` is 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 `offers` array 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.countryCode` must 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:

1. **Feed responses.** A `200` with a non-empty `offers` array (with a `pk_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.
2. **Delivered confirmations.** Each render-time confirmation returns `202 { "success": true }`. A `400` means a malformed `impressionUid` (it must be the UUID from the feed response) or a missing `deliveredTimestamp`.
3. **Attributed transactions.** Tapping `clickUrl` should land the user on the advertiser page via the 302. If you've configured the [offer\_completed webhook](/publishers/offers-api/guides/receive-completion-events), 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/feed` reference](/publishers/offers-api/reference/feed): full request/response contract, auth, status codes, image fields.
* [`POST /offers/impressions/{impressionUid}/delivered` reference](/publishers/offers-api/reference/delivered): the impression-confirmation contract.
* [API Reference overview](/publishers/offers-api/overview): the shared flow, the impression and claim models, and the selection-mode decision table.
* [Reward Users](/publishers/use-cases/post-action-reward): the use case this guide implements (moment, mechanic, economics).
