# TapResearch Publisher Documentation — Full documentation # Source: https://supply-docs.tapresearch.com · generated at build time, do not edit by hand. # Each section below is one documentation page. # Welcome to TapResearch Source: https://supply-docs.tapresearch.com/docs/3.x/intro Turn your engaged users into revenue. TapResearch rewards your users with your in-app currency for completing **surveys** — and other activities like **offers** — while we handle survey supply, matching, and reward reconciliation. ## Why TapResearch - **High-paying surveys from major research firms** — legitimate market-research surveys, matched to each user for relevance. - **Guaranteed payout** — users earn for their time even when a survey disqualifies them or fills up, so they keep coming back. - **Surveys + offers** — rewarded surveys at the core, complemented by an offerwall of app installs and other offers. - **Integrate your way** — from a low-code hosted link to a full native SDK. ## How it works 1. A user opens a TapResearch survey experience in your app. 2. They share some basic demographic info to build their profile. 3. We match them to surveys they qualify for. 4. They complete a survey, we notify your backend, and the user earns your in-app currency. ## Start here [Explore what you can offer](https://supply-docs.tapresearch.com/docs/3.x/features/overview): Surveys, the Offerwall, Bonus Bar, Currency Sales — the experiences you can put in front of your users. [Set up your account](https://supply-docs.tapresearch.com/docs/3.x/setup): Create your account and app, add a currency, and find your way around the dashboard. The same first steps for every integration. [Choose your integration](https://supply-docs.tapresearch.com/docs/3.x/choose-your-integration): Compare the four ways to integrate — from a low-code hosted link to a full native SDK — and pick the path that fits your app. --- Questions along the way? Email [developers@tapresearch.com](mailto:developers@tapresearch.com) or open your [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). --- # Features Source: https://supply-docs.tapresearch.com/docs/3.x/features/overview TapResearch is built around the **Survey Wall** — rewarded surveys your users complete to earn your in-app currency. A set of complementary features then lets you add more ways to earn, drive repeat engagement, and tune monetization. ## Core: Survey Wall [Survey Wall](https://supply-docs.tapresearch.com/docs/3.x/features/survey-wall): A full-screen wall of thousands of high-paying surveys, matched to each user and fully customizable to your app. The heart of TapResearch — delivered by the SDK, Hosted Link, and Eligibility API integrations. ## Build on it [Bonus Bar](https://supply-docs.tapresearch.com/docs/3.x/features/bonus-bar): An optional, gamified completion streak that rewards users as they finish surveys, then resets each period. [Currency Sales](https://supply-docs.tapresearch.com/docs/3.x/features/currency-sales): Run time-limited boosted exchange rates — a "2× coins" event — across surveys and offers to spike engagement and completions. [Offerwall](https://supply-docs.tapresearch.com/docs/3.x/features/offerwall): A secondary surface of non-survey offers — app installs and engagement offers — so users can earn through activities beyond surveys. [User Boosts](https://supply-docs.tapresearch.com/docs/3.x/features/user-boosts): Temporary reward multipliers for targeted user cohorts — first-timers, birthdays, win-backs, or your own custom audiences. [Standalone Profiling](https://supply-docs.tapresearch.com/docs/3.x/features/standalone-profiling): Run the profiler on its own, decoupled from the survey wall — collect profile data up front for sharper targeting. ## SDK content types Extra in-app survey formats available with the [SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integration-options) integration: [Native Survey Tiles](https://supply-docs.tapresearch.com/docs/3.x/features/native-survey-tiles): Render native survey tiles directly in your own UI, instead of opening the full wall. [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions): Short, single-question prompts you can ask your own users anywhere in your app. You can also show **sale banners** and **sale interstitials** as SDK content types — see [Placements](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/placements). --- # Survey Wall Source: https://supply-docs.tapresearch.com/docs/3.x/features/survey-wall The Survey Wall is the **core of TapResearch** — a full-screen wall of high-paying surveys from major research firms, matched to each user, where they earn your in-app currency by completing surveys. ## Why publishers choose it - **High-paying, high-converting surveys.** Users earn well and succeed more often — a bigger share of starts turn into completed, rewarded surveys. - **Consistently high fill.** A deep inventory from major research firms keeps the wall stocked, so users rarely run out of ways to earn. - **Built-in profiling.** TapResearch profiles each user and matches them to surveys they actually qualify for — more relevant surveys, fewer screen-outs, higher fill for you. - **Guaranteed payout.** Users are rewarded for their time even when they don't finish a survey — if they're disqualified or a survey is already full, they still earn a partial reward based on the time they spent. That keeps users engaged instead of hitting a dead end. ## The experience - A full-screen wall of available surveys, matched to each user. - Fully customizable to match your app's look and feel. ## Customization Make the survey wall feel native to your app under **Settings → Look & feel** in the Publisher Dashboard. Set your currency name and icon under **Settings → Currency**. ## Targeting Show content to specific groups of users by targeting cohorts with [user attributes](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes). ## Prefer native tiles? If you'd rather render surveys as tiles inside your own UI instead of opening the full wall, see [Native Survey Tiles](https://supply-docs.tapresearch.com/docs/3.x/features/native-survey-tiles). --- # Bonus Bar Source: https://supply-docs.tapresearch.com/docs/3.x/features/bonus-bar The **Bonus Bar** is a recurring, gamified reward that drives repeat engagement. Players fill a progress bar by completing surveys and unlock rewards as they reach **milestones**. The bar resets on a schedule (for example, weekly), so it's a repeatable loop rather than a one-time bonus. ## How it works - A player sees a progress bar with one or more **milestones**, each with a completion **threshold** and a **reward**. - Each qualifying survey completion advances the player's progress within the current **reset window**. - When their completion count reaches a milestone's threshold, the player earns that milestone's reward in your in-app currency. - At the end of the window the bar **resets**, and the player can earn the rewards again in the next window. Bonus Bar rewards are delivered just like any other reward in your integration — through your configured reward callback or postback — so there's nothing extra to build. ## Setting it up Configure the Bonus Bar under **Settings → Bonus bar** in the Publisher Dashboard. Create one or more **configs**; each config defines a bonus bar for a set of players. ### Config settings | Setting | Description | | ------- | ----------- | | **Currency** | The in-app currency milestone rewards are paid in. Required — pick (or create) a currency before saving. | | **Schedule** | **Weekly (recurring)** — the bar resets every week on a day and time you choose. **One-time event** — the bar runs once between a start and end date. | | **Reset day & time** *(weekly)* | The day of the week and time the bar resets each week. | | **Start & end** *(one-time)* | The window during which a one-time bar is active. | | **Audience** | Apply the config to **all players**, or target a specific player **cohort**. You can have one config per cohort plus a default for everyone else. | ### Milestones Each config has an ordered list of **milestones**. For each milestone you set: - **Completes** — the number of qualifying completions required to unlock it. Must be a positive whole number, and each milestone's threshold must be unique within the config. - **Reward** — the amount of your in-app currency the player earns when they reach the threshold. Must be a positive whole number. Add as many milestones as you like. For example: | Completes | Reward | | --------- | ------ | | 3 | 100 | | 6 | 250 | | 10 | 600 | ## Things to know - **A currency is required.** Each config pays out in one of your app's currencies. - **One config per audience.** Target different cohorts with different bonus bars, plus a default config for everyone else. - **What counts as a "complete."** Only qualifying survey completions during the current window advance a player's progress. - **Reward amounts are whole numbers.** Milestone rewards are paid as whole units of your currency. - **Availability.** The Bonus Bar is available for SDK, Hosted Link, and Eligibility API integrations. --- # User Boosts Source: https://supply-docs.tapresearch.com/docs/3.x/features/user-boosts **Boosts** temporarily multiply the rewards your users earn — a "2× coins" window for a specific group of users — to spark engagement at the moments that matter most: a user's first session, their birthday, or a win-back after they've gone quiet. :::info Enabled by TapResearch Boosts are set up and managed by the TapResearch team. **Reach out to your account manager** to turn them on for your app. ::: ## How it works You and your account manager pick **who** gets a boost, **how big** it is, and **how long** it lasts. While a user's boost is active, every survey they complete pays out at the boosted rate and the boost appears in-app under a name you choose (for example, "Welcome Bonus" or "Birthday Bonus"). ## Built-in boosts | Boost | Who it targets | Typical setup | |-------|----------------|---------------| | **First-time user** | Users in their first session | 2× for 48 hours | | **Birthday** | Users on their birthday | 2× for 24 hours | | **Churned / inactive** | Users who've lapsed (e.g. no activity for 30+ days) | 2× for 48 hours, on a trigger you define | Each has a configurable multiplier (1.5×, 2×, 3×…), display name, and time window. ## Custom boosts Beyond the built-ins, you can run **custom boosts** for anything — a seasonal promo, a launch event, a holiday weekend. Same controls: pick the audience, the multiplier, and the dates. ## Target the right users The real power is **cohort targeting** — boosts go only to the user segments you care about, defined by: - **Attributes you collect** and send to TapResearch — age, location, tenure, spend tier, or anything you pass via [user attributes](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes) or custom targeting. - **TapResearch signals** like survey history or how long since a user first appeared. Cohorts can combine filters — for example, *"users who installed 14+ days ago and have never completed a survey"* — so a boost lands exactly where it'll move the needle. ## Why publishers use them - **Activate new users** — a first-session boost drives that all-important first complete. - **Win back churned users** — a boost is a strong reason to come back. - **Reward your best users** — give high-value cohorts a bigger multiplier. ## Related - When a user receives a boost, TapResearch can notify your backend so you can surface it in your own UI — see [Boost postbacks](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/boost-postbacks). ## Turn it on Boosts are enabled per app by the TapResearch team. **[Reach out to your account manager](mailto:developers@tapresearch.com)** to get started. --- # Currency Sales Source: https://supply-docs.tapresearch.com/docs/3.x/features/currency-sales ## Overview Currency sales are **optional but highly recommended** as they drive user engagement and increase revenue. ### Content types supporting Currency Sales * **Survey wall** - Direct sale messaging in the survey wall. * **Native Survey Tiles** - Boosted rewards on your native tiles. * **Sale banner** - Display sale promotions as banners * **Sale interstitial** - Larger pop-up sale announcements Choose the content type that best fits your app's user experience. ## Promoting currency sales Let users know a sale is live with **your own messaging system**, or let TapResearch surface it for you using the **Sale banner** or **Sale interstitial** content types above. ## Configuration All content types supporting currency sales include these fields: * **Multiplier** - The multiplier applied to reward amounts * Example: A multiplier of 2 means 100 coins becomes 200 coins * **Starts on** - Sale start date and time * **Ends on** - Sale end date and time * **Interval** - How often the sale repeats ## Best practices **Recommended schedule:** * Run sales every **1-2 weeks** * Duration: **2-4 days** per sale * Run sales on **weekdays**, when survey inventory is strongest * Run less frequent **Flash Sales** with a bigger multiplier every **2-3 months** for an extra engagement boost This frequency keeps users engaged without devaluing your currency. --- # Offerwall Source: https://supply-docs.tapresearch.com/docs/3.x/features/offerwall The **Offerwall** is a wall of **non-survey offers** — primarily mobile **app installs** and multi-step engagement offers (for example, "install this game and reach level 5"). It gives you a monetization surface beyond surveys: players earn your in-app currency by completing offer **goals**. :::info Offerwall vs. Survey Wall The Offerwall is a separate product from the [Survey Wall](https://supply-docs.tapresearch.com/docs/3.x/features/survey-wall). The Survey Wall rewards users for completing **surveys**; the Offerwall rewards them for completing **app-install and engagement offers**. ::: ## The player experience - Offers appear in a **Play Games** tab next to the **Surveys** tab, where the Offerwall is enabled. - Each offer card shows its title, image, a short description, and the reward. - A player opens an offer's **details**, taps it, and is taken to the app store or offer destination. - Many offers have **multiple goals** — for example, install the app (the main goal), then reach a milestone like a level or a number of days played (bonus goals). Each goal pays out its own reward. - Players can see their **in-progress and completed** offers under **My Offers**. ## How rewards work When a player completes an offer goal, they earn your in-app currency for that goal. Because a single offer can have several goals, one offer can reward a player multiple times as they progress (install → reach level 5 → play for 7 days). Offerwall rewards are delivered just like any other reward in your integration — through your normal server-to-server reward postback — so there's nothing extra to build. ## Availability The Offerwall is **provisioned by TapResearch** for your app rather than configured in the dashboard. If you'd like to add it to your integration, contact your TapResearch account manager. A few things to know: - Offers are **platform-specific** (iOS or Android) and the available offers can vary by **country**. - The offer catalog is curated and managed by TapResearch. --- # Standalone Profiling Source: https://supply-docs.tapresearch.com/docs/3.x/features/standalone-profiling By default, TapResearch profiles each user **inside** the survey wall — they answer a few profile questions on their way into surveys. With **standalone profiling**, you can run that profiling step on its own, **decoupled from the survey wall**. :::info Enabled by TapResearch Standalone profiling is set up by the TapResearch team. **Reach out to your account manager** to enable it for your app. ::: ## What it does You place the profiler as its own experience — a quick onboarding step, for example — where users answer demographic questions and then return to your app. No survey required. Once a user is profiled, they won't be shown the standalone profiler again. ## Why decouple it - **Collect profile data up front** — build a user's profile during onboarding, before they ever reach a survey. Use this data to better understand your user base and optimize the survey experience. - **Sharper targeting, fewer screen-outs** — surveys match more accurately when a user is already profiled, so they hit fewer dead ends. - **A lighter entry point** — a short profiler is an easier first action than a full survey. ## Turn it on Standalone profiling is enabled and set up by the TapResearch team. **[Reach out to your account manager](mailto:developers@tapresearch.com)** to get started. --- # Native Survey Tiles Source: https://supply-docs.tapresearch.com/docs/3.x/features/native-survey-tiles **Native Survey Tiles** (formerly *Survey Wall Preview*) let you show available surveys as tiles in your own UI, instead of opening the full survey wall. Use the SDK guides below, or the [Survey Feed API](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview) to fetch survey tiles from your backend. - **Custom UI** — build your own presentation with survey tiles or cards. - **Direct survey access** — users tap straight into the surveys they choose. ## Platform guides [iOS](https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/ios-surveys): Integrate Native Survey Tiles in your iOS app. [Android](https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/android-surveys): Integrate Native Survey Tiles in your Android app. [React Native](https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/react-native-surveys): Integrate Native Survey Tiles in your React Native app. [Unity](https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/unity-surveys): Integrate Native Survey Tiles in your Unity app. [Flutter](https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/flutter-surveys): Integrate Native Survey Tiles in your Flutter app. --- # Quick Questions Source: https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions **Quick Questions** are short, in-app questions you ask your **own** users — up to three per prompt, surfaced automatically through the SDK, separate from the market-research surveys on the Survey Wall. Use them to capture first-party signal: app sentiment, feature interest, an NPS-style rating, or anything you'd ask in a quick prompt. You can optionally reward users for answering. :::info SDK only Quick Questions are delivered through the TapResearch SDK. They aren't available on the Hosted Link, Eligibility API, or Survey Feed API paths. ::: ## How it works You build a Quick Question in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) — multiple-choice or open-ended — and add it to a **Pop-up** placement. Matching users see it inline, with no button tap required, and when they answer your app receives a **Quick Question Response** through an SDK callback. That lets you react in the moment — for example, routing a happy user to an app-store review. The response contains the questions that were asked, the user's answers, and context like the placement, user identifier, locale, and timestamps. A simplified example: ```json { "survey_identifier": "YOUR_SURVEY_IDENTIFIER", "placement_tag": "YOUR_PLACEMENT_TAG", "user_identifier": "YOUR_USER_IDENTIFIER", "user_locale": "en", "questions": [ { "question_text": "This is the question", "question_type": "multiple_choice__single", "user_answer": { "value": "Answer F", "identifiers": ["78bf19ea…"] } } ], "complete": { "completed_at": "2025-10-13T22:31:24.869Z" } } ``` The in-app callback fires whether or not you've configured a server-to-server postback, so you can handle answers client-side without waiting on your backend. Responses can also be ingested by postback, CSV export, or the reporting API. ## Set it up Quick Questions are built in the dashboard and handled per platform: - **[Quick Questions how-to](https://supply-docs.tapresearch.com/docs/3.x/3.2-only/quick-question-how-to)** — build and launch one. - **[Handling Quick Question responses](https://supply-docs.tapresearch.com/docs/3.x/3.2-only/handling-quick-question-responses)** — the full payload and every way to ingest it. --- # Setup Source: https://supply-docs.tapresearch.com/docs/3.x/setup A checklist to get your app ready in the dashboard. Create your account, choose how you'll integrate, then set up your app for that path. ## 1. Create your account Don't have a TapResearch account yet? [Sign up](https://www.tapresearch.com/suppliers/sign_up) — it's quick. Choose the **Publisher/Developer** option. You'll need an account and an **API token** to integrate (Hosted Link doesn't use one). Account approval typically takes a few business hours. :::note Waiting for review? We typically review new accounts within a few hours. Need access sooner? Email [developers@tapresearch.com](mailto:developers@tapresearch.com). ::: ## 2. Choose your integration Decide how you'll integrate **before** you create your app — your choice determines how the app is set up. Compare the four paths and pick the one that fits your product. → [Choose your integration](https://supply-docs.tapresearch.com/docs/3.x/choose-your-integration) ## 3. Create your app Create your app from the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) and select the **integration path** you chose above. That configures the app correctly for you behind the scenes. - **SDK** — select each platform you ship on (**iOS**, **Android**, or both). Each platform gets its own **API token**, so use the matching token in each build. - **Eligibility API** or **Survey Feed API** — **one app is all you need.** The endpoint is the same across every surface (mobile *and* web), so a single app and token covers them all. You can create separate apps for different paths. ## 4. Configure app settings - **Look & feel** — survey wall colors and branding, under **Settings → Look & feel**. - **App store identifier** (SDK apps only) — set it now, or leave it blank and fill it in later. - **Postback URL** — where TapResearch sends rewards to your server. SDK integrations can use in-app callbacks instead. See Handling rewards for your path: [SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards), [Hosted Link](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards), [Eligibility API](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards), or [Survey Feed API](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards). - **Redirect URL** (Survey Feed API only) — where participants return after a survey, under **Settings → App title settings**. See [Redirect URLs](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects). ## 5. Add a currency A currency is **required** before you can create content. Set its name, display name, image, exchange rate, and (recommended) a profile reward. → [Currencies](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/currencies) · [Exchange rates](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/exchange-rate) · [Profile reward](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/profile-reward) ## 6. Add content to a placement On your app's **Manage** tab, click **Create content** to add content to a placement. → [Placements](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/placements) ## 7. Build your integration Now that your app is set up, follow your path's guide to build it. → [SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integration-options) · [Hosted Link](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/setup) · [Eligibility API](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/has-offer) · [Survey Feed API](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview) ## 8. Test and go live Until you go live, only **test devices** can access content. Add a test device, verify everything in test mode, then promote your app to live from its **Integration** tab. → [Testing & going live](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa) ## Access the Publisher Dashboard Once you have an account, the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) is your home for managing apps, updating settings, accessing your API token, and monitoring performance. --- # Choose your integration Source: https://supply-docs.tapresearch.com/docs/3.x/choose-your-integration TapResearch offers **four integration paths**. They share the same survey supply and reward postbacks, but differ in how much you build versus how much TapResearch handles for you — and in how you check whether a user is eligible before you send them. Use the cards below to jump straight to a path, or compare them feature-by-feature in the [At a Glance](https://supply-docs.tapresearch.com/docs/3.x/choose-your-integration#at-a-glance) matrix. :::note Mobile vs. web The **SDK** is for native **mobile** apps (iOS / Android). On **web or desktop**, use the **Hosted Link**, **Eligibility API**, or **Survey Feed API** — they're a URL or a server-side call, so they work anywhere. ::: [SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integration-options) (Recommended): Our full-featured native integration. The SDK renders survey placements in your app and handles eligibility, ranking, profiling, and reward callbacks for you. Its hidden-webview logic updates on TapResearch's side, so behavior improves without you shipping a new SDK release. [Hosted Link](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/overview) (Fastest): The fastest, low-code path. Integrate a single URL — the same link for every user — and TapResearch handles eligibility and survey selection. There is no SDK to install and no API to call; you only template the link per user. [Eligibility API](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/overview) (Pre-send check): A lightweight server-side API that tells you whether a user has an eligible survey before you show a call-to-action — a simple yes/no plus a hand-off link, not the survey list. [Survey Feed API](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview) (Control + impanel): Full control over the experience. Fetch **the surveys themselves** and render them in your own UI, with profile / impanel support for a profile-first flow. This is the only path that impanels participants. ## At a glance
Comparison SDK Hosted Link Eligibility API Survey Feed API
What you build An entry point and reward handling A button and the link A server-side check + hand-off link Survey list UI, webview, reward handling
Survey runs in In-app, we render Mobile browser Mobile browser Your in-app webview
User stays in your app ✓ ✕ ✕ ✓
Control over presentation Low Low Low Full
Effort and time to launch Low Lowest Low Highest
Reward notifications In-app callback or server-to-server Server-to-server Server-to-server Server-to-server
Native Survey Tiles ✓ ✕ ✕ ✓
Quick Questions ✓ ✕ ✕ ✕
In-app promotions ✓ ✕ ✕ ✕
Who handles survey abandons We do We do We do You do
Eligibility check ✓ ✕ ✓ ✓
:::tip Choosing for mobile? If you're integrating a mobile app (**iOS or Android**), start with the **[SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integration-options)** path. It delivers the richest, most native experience — in-app survey tiles, Quick Questions, and automatic profiling — and its hidden-webview logic keeps improving without you shipping a new SDK release. ::: --- # SDK overview Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integration-options The TapResearch SDK is the fastest way to add surveys to a native app. It handles survey retrieval, ranking, eligibility, profiling, and reward callbacks for you. :::tip Creating your app In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), create a new app and select **SDK**, then the platforms you'll ship on (iOS, Android, or both). See the [Setup checklist](https://supply-docs.tapresearch.com/docs/3.x/setup) for the full flow. ::: There are **two ways to present surveys** with the SDK — use either, or both together. Survey wall (standard) Add a call-to-action — a button, banner, or menu item. When the user taps it, TapResearch opens the full-screen survey wall in a web view and takes over. The quickest path: one entry point, and we handle the whole experience. Native Survey Tiles The SDK returns an array of the surveys a user qualifies for, so you render them as native tiles in your own UI — reward amount, estimated time, and all. The user taps a tile and that survey launches directly. You can mix the two — for example, surface a few preview tiles in your feed and a CTA into the full wall elsewhere. :::info Platform guides [iOS](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios) · [Android](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android) · [React Native](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native) · [Unity](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity) · [Flutter](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter) ::: ## What the SDK handles for you - **Profiling & targeting** — users are matched to surveys by demographics and eligibility automatically. - **Quick Questions** — short, in-app questions you can ask your own users; only available through the SDK. See [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/3.2-only/quick-question-how-to). - **Survey ranking, eligibility filtering, and reward callbacks** — handled for you, so integration stays light. :::tip Want a fully custom, server-side experience? If you'd rather fetch surveys from your backend and render everything in your own UI without the SDK, use the [Survey Feed API](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview) instead. ::: --- # Integrate with AI Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integrate-with-ai Prefer to let an AI coding agent do the heavy lifting? Pick your platform and copy the prompt below into Claude, ChatGPT, Cursor, Codex — whatever you use. It points the agent at our full, always-current SDK reference for that platform, so it builds from the real documentation instead of guessing. Your agent can scaffold initialization, reward callbacks, and placement display from there. You stay in control — review what it writes, and dive into the rest of these docs whenever you want the details yourself. ## After you integrate: verify it's working Once the code is in place, run through these checks on a **test device**. **Your AI agent can help** — it can write a smoke test, walk you through each step, and debug failures. What it *can't* do is see your account, so your app's **Integration** tab in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) is what confirms each step actually passed — it detects each one from real activity and marks it done. :::tip Use a real test session These checks only register from a real test user reaching the SDK — firing a test postback on its own won't complete them. Add a test device first (**More → Test devices**); only test users can initialize the SDK before go-live. ::: 1. **SDK initialization received** — Initialize the SDK with your app's API token. The Integration tab marks this once it sees your first session. 2. **Enter survey wall from app** — Show a placement and reach the survey wall. (You'll need a placement with content configured on the **Manage** tab first.) 3. **Reward callback configured** — Set a server-to-server postback URL (this auto-completes), or implement the in-app reward handler. 4. **Generate test survey complete** — Complete a test survey so a completion is recorded against your app. 5. **Reward redeemed for test user** — Confirm your postback handler or in-app reward callback redeems the reward for the test user. If a step doesn't check off, it's the fastest signal of where the integration broke. For deeper testing guidance, see [Testing & QA](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa). --- # iOS Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios :::tip What you'll do 1. [Install the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#installation) 2. [Set up reward callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#callbacks) 3. [Initialize the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#initialization) 4. [Display a placement](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#displaying-a-placement) Later sections cover user attributes, the user identifier, placement details, custom parameters, and more. **Testing?** Your app starts in test mode, so you'll need a [test device](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa) to see content while you build. ::: ## Installation See the [iOS example apps](https://github.com/TapResearch/demo-apps/tree/master/ios) on GitHub. :::note Minimum iOS version: 13.0, or 15.0 for SDK 3.8.1 and later. ::: Replace `` below with your SDK version from [GitHub releases](https://github.com/TapResearch/TapResearch-iOS-SDK/releases). ### CocoaPods ```ruby title="Podfile" platform :ios, '13.0' # Use '15.0' for SDK 3.8.1 and later. use_frameworks! target 'MyApp' do pod 'TapResearch', '' # Omit the GitHub tag's leading "v". end ``` Then run `pod install` in the Terminal. ### Swift Package Manager Add the following URL in the Xcode Swift Package Manager: ```text https://github.com/TapResearch/TapResearch-iOS-SDK ``` * Set the Dependency Rule to **Exact Version** or **Up to Next Major Version** and enter your SDK version * Set "Add to Project" to your project * Click "Add Package" In the resulting "Choose Package Product for TapResearch-iOS-SDK" select the TapResearchSDK row and click "Add Package". :::note Adding the package with an AI agent An agent running **outside** Xcode (a terminal or IDE agent) can add this Swift Package by editing your Xcode project file directly. An agent running **inside** Xcode (for example Xcode's built-in coding assistant) can't — Xcode holds the project file open, so it can't be modified while Xcode is running. In that case, add the package yourself with the steps above, then hand control back to the agent to continue. ::: ### Manual installation Download the source archive for a release from [GitHub releases](https://github.com/TapResearch/TapResearch-iOS-SDK/releases), drag `TapResearchSDK.xcframework` into your project, and set it to **Embed & Sign** under **Frameworks, Libraries, and Embedded Content**. ### Importing the SDK Import the SDK in each file that calls it: ```swift import TapResearchSDK ``` ```objectivec #import ``` ## Integration ### Callbacks The TapResearch SDK has two main delegates you can implement: `TapResearchSDKDelegate` and `TapResearchContentDelegate`. If you are implementing the delegates as their own objects make sure that the delegate objects derive from `NSObject`, for example in Swift: `class MyDelegates: NSObject, TapResearchSDKDelegate {`. #### TapResearchSDKDelegate - **`onTapResearchSdkReady`** (required): Called when the SDK is ready for use. **We recommend showing content only after this callback is called.** - **`onTapResearchDidError`** (required): Called when an error has occurred in the TapResearch SDK. For the full list, see the [error codes page](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/sdk-error-codes). - **`onTapResearchDidReceiveRewards`** (optional): Called when a user has earned a reward(s). You can implement it here or set a separate [reward delegate](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#tapresearchrewarddelegate). - **`onTapResearchQuickQuestionResponse`** (optional): Called when a quick question result is being returned. The class that conforms to the `TapResearchSDKDelegate` protocol (ideally your `SceneDelegate`) must implement `onTapResearchSdkReady` and `onTapResearchDidError`. ##### Reward object Each reward is a `TRReward`. See [TRReward](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward) for its properties, including the iOS `payoutEvent` values. ##### Quick Question response New to Quick Questions? See [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions) for what they are and the response payload. `onTapResearchQuickQuestionResponse` is an optional method of **TapResearchSDKDelegate**. It is called when a Quick Question response is being returned, and receives a **TRQQDataPayload** object. Use a class to implement the SDK's delegates and pass it to TapResearch at initialization. In UIKit you can adopt the protocol on your `SceneDelegate` instead. ```swift title="TapResearchSDKDelegates.swift" import TapResearchSDK class TapResearchSDKDelegates: NSObject, TapResearchSDKDelegate { func onTapResearchSdkReady() { // The SDK is ready: send user attributes, show content, etc. if let error = TapResearch.sendUserAttributes(attributes: ["attribute1": "some player attribute", "a_number": 12]) { print("sendUserAttributes: \(error.code) \(error.localizedDescription)") } } func onTapResearchDidError(_ error: NSError) { print("onTapResearchDidError: \(error.code) \(error.localizedDescription)") } func onTapResearchDidReceiveRewards(_ rewards: [TRReward]) { for reward in rewards { print("reward: currency = \(reward.currencyName ?? "unknown"), amount = \(reward.rewardAmount)") } } func onTapResearchQuickQuestionResponse(_ qqPayload: TRQQDataPayload) { for question: TRQQDataPayloadQuestion in qqPayload.questions { // ... } } } ``` ```objectivec title="SceneDelegate.m" @interface SceneDelegate () @end @implementation SceneDelegate - (void)onTapResearchSdkReady { NSLog(@"onTapResearchSdkReady"); } - (void)onTapResearchDidError:(NSError *)error { NSLog(@"onTapResearchDidError: %ld %@", (long)error.code, error.localizedDescription); } - (void)onTapResearchDidReceiveRewards:(NSArray *)rewards { NSLog(@"onTapResearchDidReceiveRewards: %lu reward(s)", (unsigned long)rewards.count); } - (void)onTapResearchQuickQuestionResponse:(TRQQDataPayload *)qqPayload { NSLog(@"onTapResearchQuickQuestionResponse: %@", qqPayload); } @end ``` #### TapResearchRewardDelegate - **`onTapResearchDidReceiveRewards`**: Called when a user has earned a reward(s). Implementing `onTapResearchDidReceiveRewards` on your `TapResearchSDKDelegate` is optional. If you implement it, rewards can be sent to your app as soon as the SDK is ready. If not, your app will not receive any rewards until a reward delegate has been set. ##### Setting a reward delegate To set a reward handler when you are ready to receive rewards, use: ```swift TapResearch.setRewardDelegate(myRewardDelegate) ``` ```objectivec [TapResearch setRewardDelegate:myRewardDelegate]; ``` The object you pass in must conform to `TapResearchRewardDelegate` and implement `onTapResearchDidReceiveRewards`. When you no longer need the reward delegate you can simply pass `nil` to the same SDK function. :::warning If you set the reward delegate from a view controller, for example in `viewDidLoad`, make sure to call `TapResearch.setRewardDelegate(nil)` when the view controller is no longer in the stack. ::: :::note Anytime you set a new reward delegate, or nil, it replaces the previously set value. For example: if you have a reward delegate available at init time then later set another reward delegate, the original delegate will be replaced by the new one. ::: #### TapResearchQuickQuestionDelegate - **`onTapResearchQuickQuestionResponse`**: Called when a quick question result is being returned. To set a Quick Questions response handler when you are ready to receive Quick Question responses, use: ```swift TapResearch.setQuickQuestionDelegate(myQuickQuestionDelegate) ``` ```objectivec [TapResearch setQuickQuestionDelegate:myQuickQuestionDelegate]; ``` The object you pass in must conform to `TapResearchQuickQuestionDelegate` and implement `onTapResearchQuickQuestionResponse`. When you no longer need the Quick Questions response delegate you can simply pass `nil` to the same SDK function. :::warning If you set the Quick Questions response delegate from a view controller, for example in `viewDidLoad`, make sure to call `TapResearch.setQuickQuestionDelegate(nil)` when the view controller is no longer in the stack. ::: :::note Anytime you set a new Quick Questions response delegate, or nil, it replaces the previously set value. For example: if you have a Quick Questions response delegate available at init time then later set another Quick Questions response delegate, the original delegate will be replaced by the new one. ::: #### TapResearchContentDelegate - `onTapResearchContentShown`: Called when content is successfully presented to the user. - `onTapResearchContentDismissed`: Called when content is dismissed. You can use these callbacks to pause/resume audio, animations, etc. when content is shown or dismissed. ```swift title="TapResearchContentDelegates.swift" class TapResearchContentDelegates: NSObject, TapResearchContentDelegate { func onTapResearchContentShown(forPlacement placement: String) { print("\(#function): \(placement) was shown") } func onTapResearchContentDismissed(forPlacement placement: String) { print("\(#function): \(placement) was dismissed") } } ``` ```objectivec title="ViewController.m" @interface ViewController () @end @implementation ViewController - (void)onTapResearchContentShownForPlacement:(NSString *)placement { NSLog(@"onTapResearchContentShown(%@)", placement); } - (void)onTapResearchContentDismissedForPlacement:(NSString *)placement { NSLog(@"onTapResearchContentDismissed(%@)", placement); } @end ``` ### Initialization :::tip We recommend initializing TapResearch as early as possible in your application's lifecycle. This will ensure that your placements are refreshed by the time you are ready to show content. ::: To initialize the SDK, you'll need your app's API token, shown on your app's **Settings** tab in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). Refer to the [app setup guide](https://supply-docs.tapresearch.com/docs/3.x/setup#configure-app-settings) for more information. After obtaining your API Token, add the following snippet to the `scene:willConnectToSession:options:` method in your `SceneDelegate` class. Replace `API_TOKEN` with your API token and `UNIQUE_USER_ID` with a unique identifier within your application. :::warning Don't put the token in Info.plist Keep your API token and placement tag in a **separate Swift config file** (for example `TapResearchCredentials.swift`) and pass them to `initialize`. Do **not** add them to `Info.plist` — TapResearch never reads them from there, and on SwiftUI projects where `Info.plist` is auto-generated, hand-creating one to hold credentials will break your build. The token is only ever used in the `initialize` call. ::: If initializing the TapResearch SDK isn't possible inside the `scene:willConnectToSession:options:` method, try and do it as early as possible to ensure that your placements are refreshed by the time you are ready to show content. :::tip The user identifier passed through initialization must be **unique**! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service. ::: ```swift title="SceneDelegate.swift" func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let _ = (scene as? UIWindowScene) else { return } // SceneDelegate conforms to TapResearchSDKDelegate TapResearch.initialize(withAPIToken: apiToken, userIdentifier: userIdentifier, sdkDelegate: self) { (error: NSError?) in if let error = error { print("\(error.code) \(error.localizedDescription)") } } } ``` :::info Initialize the SDK in the `init()` method of your root view. ::: ```swift title="MainContentView.swift" let tapSDKDelegates: TapResearchSDKDelegates init() { tapSDKDelegates = TapResearchSDKDelegates() TapResearch.initialize(withAPIToken: apiToken, userIdentifier: userId, sdkDelegate: self.tapSDKDelegates) { (error: NSError?) in if let error = error { print("\(#function): \(error.code), \(error.localizedDescription)") } } } ``` ```objectivec title="SceneDelegate.m" - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // SceneDelegate conforms to TapResearchSDKDelegate [TapResearch initializeWithAPIToken:API_TOKEN userIdentifier:UNIQUE_USER_ID sdkDelegate:self completion:^(NSError * _Nullable error) { if (error) { NSLog(@"Error on initialize: %ld, %@", (long)error.code, error.localizedDescription); } }]; } ``` :::note To change the user after the SDK has started, call [`setUserIdentifier`](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#setting-the-user-identifier). ::: ### Send user attributes :::info See [User attributes and targeting](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes) for more. ::: User attributes are used to target specific users with content (ex: an offer). The most recent values received are used when determining which content to show. Pass a dictionary of attributes to `sendUserAttributes` after the SDK is ready. Attribute names must be strings and must not begin with `tapresearch_`, which is reserved for TapResearch. Use string or numeric values for targeting. For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. Set `clearPreviousAttributes` to `false` to merge new values with existing attributes, or `true` to replace the attributes you previously sent. ```swift title="ViewController.swift" func sendUserAttributes() { let date = Date() // Current date and time let formatter = ISO8601DateFormatter() let iso8601String = formatter.string(from: date) let dict: [AnyHashable: Any] = ["app-user": userIdentifier, "roller-type": "high", "roller-type-valid-until": Date().timeIntervalSince1970 + ViewController.threeHours, "some-date": iso8601String, "some-double": 1.11] if let error = TapResearch.sendUserAttributes(attributes: dict, clearPreviousAttributes: true) { print(error.localizedDescription) } } ``` ```objectivec title="ViewController.m" - (void)sendUserAttributes { NSDate *currentDate = [NSDate date]; NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init]; NSString *iso8601String = [formatter stringFromDate:currentDate]; NSTimeInterval threeHours = 3 * 60 * 60; NSNumber *rollerTypeValidUntil = @([currentDate timeIntervalSince1970] + threeHours); NSNumber *someDouble = @1.11; NSDictionary *dict = @{ @"app-user": userIdentifier, @"roller-type": @"high", @"roller-type-valid-until": rollerTypeValidUntil, @"some-date": iso8601String, @"some-double": someDouble }; NSError *error = [TapResearch sendUserAttributesWithAttributes:dict clearPreviousAttributes:YES]; if (error) { NSLog(@"%@", [error localizedDescription]); } } ``` You can also pass user attributes when you first initialize the SDK. This is done by changing the `TapResearch.initialize(...)` call to additionally pass a `userAttributes` dictionary and the `clearPreviousAttributes` flag. :::info If user attributes are known at SDK initialization, it is preferable to pass them through `initialize` compared to using `sendUserAttributes`. This will result in quicker load times for targeted content. ::: ```swift let dict: [String: Any] = ["app-user": userIdentifier, "roller-type": "high", "roller-type-valid-until": Date().timeIntervalSince1970 + ViewController.threeHours] TapResearch.initialize(withAPIToken: apiToken, userIdentifier: userIdentifier, userAttributes: dict, clearPreviousAttributes: true, sdkDelegate: self) { (error: NSError?) in if let error = error { print(error.localizedDescription) } } ``` ```objectivec NSDictionary *dict = @{ @"string": @"a string", @"number": @1 }; [TapResearch initializeWithAPIToken:apiToken userIdentifier:userIdentifier userAttributes:dict clearPreviousAttributes:YES sdkDelegate:self completion:^(NSError * _Nullable error) { if (error) { NSLog(@"Error on initialize: %ld, %@", (long)error.code, error.localizedDescription); } }]; ``` ### Setting the user identifier You can change the user identifier after the SDK is ready by calling `setUserIdentifier` with a string. ```swift TapResearch.setUserIdentifier("some-user-id") ``` ```objectivec [TapResearch setUserIdentifier:@"some-user-id" completion:nil]; ``` ### Checking if the SDK is ready The SDK lets the app know it is ready using a callback: ```swift func onTapResearchSdkReady() { } ``` ```objectivec - (void)onTapResearchSdkReady { } ``` You can also check if the SDK is ready using the `isReady` call: ```swift let ready: Bool = TapResearch.isReady() ``` ```objectivec BOOL ready = [TapResearch isReady]; ``` ### Getting placement details :::info `getPlacementDetails` is available from v3.6.3 ::: :::warning Don't cache placement details Placement details can change as our SDK refreshes placements in the background, so it is not recommended to store or cache these values. Instead, refetch the details each time you need to use them, e.g. to display something in your UI. ::: To get details for a placement call `getPlacementDetails`: ```swift let details: TRPlacementDetails? = TapResearch.getPlacementDetails("your-placement-tag") { (error: NSError?) in // Handle error } if let details { // Use the placement details } ``` ```objectivec TRPlacementDetails *details = [TapResearch getPlacementDetails:@"your-placement-tag" errorHandler:^(NSError * _Nullable error) { if (error) { // Handle the error } }]; if (details) { // Use the placement details } ``` #### The `TRPlacementDetails` objects: ```swift @objc public final class TRPlacementDetails : NSObject, Encodable { @objc public let name : String @objc public let contentType : String @objc public let currencyName : String @objc public let isSale : Bool @objc public let saleType : String? @objc public let saleEndDate : String? @objc public let saleMultiplier : Double @objc public let saleDisplayName : String? @objc public let saleTag : String? @objc public let bonusBarProgress : TRBonusBarProgress? } @objc public final class TRBonusBarProgress : NSObject, Encodable { @objc public let isActive : Bool @objc public let currentCompletes : Int @objc public let bonusWindowEndAt : String? @objc public let bonusTiers : [TRBonusTier]? @objc public let error : NSError? } @objc public final class TRBonusTier : NSObject, Encodable { @objc public let tierNumber : Int @objc public let completesNeeded : Int @objc public let rewardAmount : Int @objc public let status : String? } ``` ### Displaying a placement Placements are locations and moments inside the game when content can be shown. To show content for a placement, you'll need to specify a placement `tag`. Note that the show content calls are wrapped in a `canShowContent` check. This is to ensure that the placement is ready to be shown. If the placement is not ready, the SDK will return `false`. This helps guard against showing the user something that is not ready to be shown yet. `canShowContent` accepts an error block. It is optional in Swift; in Objective-C you must pass a block or `nil`. If there is an error while showing a placement, it will be returned in the completion handler. The error will contain a code and a description. Placement tags are listed on your app's **Manage** tab in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). ```swift title="ViewController.swift" let canShow: Bool = TapResearch.canShowContent(forPlacement: placementTag) { (error: NSError?) in // Optional: handle the error } if canShow { let customParameters: [String: Any] = ["param1": 123, "param2": "abc"] TapResearch.showContent(forPlacement: placementTag, delegate: self, customParameters: customParameters) { (error: NSError?) in if let error = error { print("\(error.code) \(error.localizedDescription)") } } } ``` ```swift title="ContentView.swift" if TapResearch.canShowContent(forPlacement: placementTag) { let customParameters: [String: Any] = ["param1": 123, "param2": "abc"] TapResearch.showContent(forPlacement: placementTag, delegate: self.tapResearchDelegates, customParameters: customParameters) } else { print("Placement \(placementTag) not ready") } ``` ```objectivec title="ViewController.m" BOOL canShow = [TapResearch canShowContentForPlacement:placementTag error:^(NSError * _Nullable error) { // Optional: handle the error }]; if (canShow) { [TapResearch showContentForPlacement:placementTag delegate:self completion:^(NSError * _Nullable error) { if (error) { NSLog(@"Error on showContent: %ld, %@", (long)error.code, error.localizedDescription); } }]; } ``` ### Passing custom parameters Custom parameters allow you to receive additional information about the user when they complete a survey. This can be used to filter out users who have already completed a survey, or to target specific users. These params will be returned in your [server-to-server postbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#server-to-server-callbacks). There are a maximum of 5 custom parameters that can be passed per placement. The below code is a zoomed-in view of the code above. :::note Parameters can only be ASCII characters; Unicode is not supported. Values must be strings, numbers, or booleans. ::: ```swift let customParameters: [String: Any] = ["param1": 123, "param2": "abc"] TapResearch.showContent(forPlacement: placementTag, delegate: self, customParameters: customParameters) { (error: NSError?) in if let error = error { print("Error \(error.code) \(error.localizedDescription)") } } ``` ```objectivec title="ViewController.m" [TapResearch showContentForPlacement:placementTag delegate:self customParameters:@{@"custom_param_1": @"test text", @"custom_param_2": @12} completion:^(NSError * _Nullable error) { if (error) { NSLog(@"Error on showContent: %ld, %@", (long)error.code, error.localizedDescription); } }]; ``` ### Granting boosts You can grant a boost to a user with the `grantBoost` function using Boosts you have set up for your app. First implement the `TapResearchGrantBoostResponseDelegate` to receive the result of granting a boost: ```swift func onTapResearchGrantBoostResponse(_ response: TapResearchSDK.TRGrantBoostResponse) { if response.success { // Perhaps update display in response to a successful boost } else { if let error = response.error { // Handle the error } } } ``` Grant a boost: ```swift // Assuming self implements TapResearchGrantBoostResponseDelegate TapResearch.grantBoost("BOOST_TAG", delegate: self) { (error: NSError?) in // Handle any error } ``` First implement the `TapResearchGrantBoostResponseDelegate` to receive the result of granting a boost: ```objectivec - (void)onTapResearchGrantBoostResponse:(TRGrantBoostResponse * _Nonnull)response { if (response.success) { // Perhaps update display to indicate boost granted } else { if (response.error) { // Handle the error } } } ``` Grant a boost: ```objectivec // Assuming self implements TapResearchGrantBoostResponseDelegate [TapResearch grantBoost:@"YOUR_BOOST_TAG" delegate:self errorHandler:^(NSError * _Nullable error) { // Handle any error }]; ``` #### The `TRGrantBoostResponse` object: ```swift @objc public final class TRGrantBoostResponse : NSObject, Encodable { @objc public let boostTag : String @objc public let success : Bool @objc public let error : NSError? } ``` ## NotificationCenter events Available from SDK `3.8.0--rc0`. Instead of implementing a delegate method for some callbacks, you can observe a notification on the default `NotificationCenter`. | Notification name | Posted when | Replaces | `userInfo` | |---|---|---|---| | `TapResearch.TapResearchSDKReady` | The SDK is ready to be used | `onTapResearchSdkReady` (`TapResearchSDKDelegate`) | — | | `TapResearch.TapResearchDidError` | An error occurs | `onTapResearchDidError` (`TapResearchSDKDelegate`) | `TapResearch.ErrorKey` → `NSError` | | `TapResearch.TapResearchContentShown` | TapResearch content is shown | `onTapResearchContentShown` (`TapResearchContentDelegate`) | `TapResearch.PlacementTagKey` → placement tag `String` | | `TapResearch.TapResearchContentDismissed` | TapResearch content is dismissed | `onTapResearchContentDismissed` (`TapResearchContentDelegate`) | `TapResearch.PlacementTagKey` → placement tag `String` | To register, add an observer for each notification you need, for example `TapResearch.TapResearchContentShown`: ```swift func registerForNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(handleTapResearchNotification(_:)), name: TapResearch.TapResearchContentShown, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleTapResearchNotification(_:)), name: TapResearch.TapResearchDidError, object: nil) } @objc func handleTapResearchNotification(_ notification: Notification) { if let placementTag = notification.userInfo?[TapResearch.PlacementTagKey] as? String { // Content shown (or dismissed) for placementTag } if let error = notification.userInfo?[TapResearch.ErrorKey] as? NSError { // Handle the error } } ``` ## Need help? If you're experiencing issues or need assistance, reach out to [developers@tapresearch.com](mailto:developers@tapresearch.com). --- **Stay updated:** watch the [TapResearch iOS SDK on GitHub](https://github.com/TapResearch/TapResearch-iOS-SDK) to get notified about new releases. --- # iOS Native Survey Tiles Source: https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/ios-surveys :::info Test mode If your app is in test mode, you **must** use a test user. Add test users in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under **More → Test devices**. ::: ## Installation Start with the [iOS integration guide](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios) if you haven't set up the SDK yet. See the [Native Survey Tiles example app](https://github.com/TapResearch/demo-apps/tree/master/ios/BasicDemos/SurveyWallPreview) on GitHub. ### Before you begin * You must be on SDK v3.4.2+ to have access to Native Survey Tiles. * The wall preview feature needs to be enabled and set up by the TapResearch team so please reach out to your account manager when you are ready to get started * Native Survey Tiles should be on a dedicated placement, separate from the standard Survey Wall (if applicable) * **Always wait** until the SDK is ready (via the `onTapResearchSdkReady` callback or by checking `TapResearch.isReady()`) before calling any Native Survey Tiles interfaces * Set a `TapResearchSurveysDelegate` (its `onTapResearchSurveysRefreshed(forPlacement:)` method) before calling `getSurveys` or `showSurvey`, so your app hears when the surveys change. :::info Support Any other questions? Reach out to your account manager or via Slack and we'll be happy to help! ::: ## Introduction **Native Survey Tiles** (formerly *Survey Wall Preview*) let you provide users with available surveys, directly inside your app. Some benefits of this include: * **Custom UI** — Create your own UI presentation with survey tiles/cards * **Direct survey access** — Provide users with direct entry into surveys they choose --- ### Key considerations & UI design When designing your survey tiles/cards, consider these elements: ### 1. The profile survey New users must complete the introductory profile survey before they can access other surveys. For these new users, the SDK will return **only** this survey until they have completed it. * Only shown to users that are new to TapResearch * Will always be returned by itself, with a survey identifier of `profile` * Display example: ### 2. Survey details After completing the profile survey, users should then start receiving real surveys. Surveys will have the following attributes: ```swift @objc public final class TRSurvey : NSObject, Codable { // Primary attributes @objc public let surveyIdentifier : String // Unique identifier, will be used when a user taps on a survey @objc public let lengthInMinutes : Int // The estimated time to complete the survey @objc public let rewardAmount : Double // The reward amount for taking the survey @objc public let currencyName : String // The name of the reward currency @objc public let isHotTile : Bool // Whether the survey should be designated as a "hot tile" @objc public let category : String? // The survey's category, if it has one // Sale attributes @objc public let isSale : Bool // Whether a currency sale is currently active @objc public let saleMultiplier : Double // The multiplier being applied to the sale @objc public let saleEndDate : String? // If a sale is active, the UTC date it will end @objc public let preSaleRewardAmount : Double // The pre-multiplier reward amount, for enhancing UI during a sale } ``` * We recommend displaying all primary data points to allow users to choose which survey length/reward they want * Display example: ### 3. "Hot" tiles Hot tiles are surveys with exceptional scores, typically offering the best time-to-reward ratio. Displaying a special UI highlight for these surveys is recommended, as they tend to increase user engagement and click-through rates. * Display example: ### 4. Currency Sales When running promotional currency sales, the relevant sale attributes of the survey object will populate, allowing you to show enhanced UIs and CTAs to users. We recommend using this data to display the extra rewards boost on the individual tiles. * Display example: --- ## Integration ### SDK interfaces #### Checking for available surveys When you have received notification that the SDK is ready (see [Checking if the SDK is ready](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#checking-if-the-sdk-is-ready)) you should first check if there are surveys available for your placement: ```swift let hasSurveys: Bool = TapResearch.hasSurveys(for: placementTag) { (error: NSError?) in // Optional: handle the error } ``` ```objectivec BOOL hasSurveys = [TapResearch hasSurveysFor:placementTag errorHandler:^(NSError * _Nullable error) { // Optional: handle the error }]; ``` #### Getting available surveys Get the surveys for your placement. Note: Fetch surveys only after receiving the SDK Ready callback. ```swift let surveys: [TRSurvey] = TapResearch.getSurveys(for: placementTag) ``` ```objectivec NSArray *surveys = [TapResearch getSurveysFor:placementTag errorHandler:nil]; ``` #### Check that a survey can be shown Before showing, it is always best to check if the survey can be shown using `canShowSurvey`: ```swift let canShow: Bool = TapResearch.canShowSurvey(surveyId: survey.surveyIdentifier, forPlacementTag: placementTag) ``` ```objectivec BOOL canShow = [TapResearch canShowSurveyWithSurveyId:survey.surveyIdentifier forPlacementTag:placementTag]; ``` #### Showing a survey Showing a survey works similarly to `showContent` used for showing TapResearch survey content: after checking if a survey can be shown, call `showSurvey` with a survey ID, placement tag and a content delegate to receive `onTapResearchContentShown` and `onTapResearchContentDismissed` callbacks, see [TapResearchContentDelegate](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#tapresearchcontentdelegate) for details. ```swift TapResearch.showSurvey(surveyId: survey.surveyIdentifier, placementTag: placementTag, delegate: self) { (error: NSError?) in // Handle the error } ``` ```objectivec [TapResearch showSurveyWithSurveyId:survey.surveyIdentifier placementTag:placementTag delegate:self errorHandler:^(NSError * _Nullable error) { // Handle the error }]; ``` If you need to pass custom parameters use: ```swift TapResearch.showSurvey(surveyId: survey.surveyIdentifier, placementTag: placementTag, delegate: self, customParameters: ["param1": 123, "param2": "abc"]) { (error: NSError?) in // Handle the error } ``` ```objectivec [TapResearch showSurveyWithSurveyId:survey.surveyIdentifier placementTag:placementTag delegate:self customParameters:@{@"param1": @123, @"param2": @"abc"} errorHandler:^(NSError * _Nullable error) { // Handle the error }]; ``` ### Callback Native Survey Tiles adds a callback that lets you know when surveys have been refreshed. #### Setting the delegate Set the delegate using `setSurveysDelegate` before calling `getSurveys` or `showSurvey`. Without it, your app isn't told when the surveys for a placement change. ```swift TapResearch.setSurveysDelegate(self) ``` ```objectivec [TapResearch setSurveysDelegate:self]; ``` To remove the delegate call the `setSurveysDelegate` with `nil`. #### Handling the callback You will see calls to the callback function when surveys have been completed, after any TapResearch content has been dismissed. Make sure the delegate object implements `TapResearchSurveysDelegate` and updates local storage and displays with the updated surveys: ```swift func onTapResearchSurveysRefreshed(forPlacement placementTag: String) { let surveys: [TRSurvey] = TapResearch.getSurveys(for: placementTag) // Update your survey tiles } ``` ```objectivec - (void)onTapResearchSurveysRefreshedForPlacement:(NSString *)placementTag { NSArray *surveys = [TapResearch getSurveysFor:placementTag errorHandler:nil]; // Update your survey tiles } ``` --- # iOS orientation handling Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios-alternate-orientation-handling It's important that landscape apps not lock the screen rotation when rendering TapResearch. Landscape orientation within the TapResearch experience would result in degraded engagement and revenue performance. By contrast, apps that allow rotation to portrait mode for TapResearch screens typically achieve twice the earnings of apps that are locked in landscape. For the cross-platform guidance that applies to every SDK platform, see [Screen Orientation](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/screen-orientation). ## Alternate orientation handling If you are in a situation where enabling portrait for iPhone is not possible in Xcode's project settings you can use this alternate method for allowing portrait for the TapResearch iOS SDK. **This assumes you are not using a SceneDelegate.** ## 1. Set allowed orientations. In the Xcode project editor's "General" tab set the required orientations in the "Deployment Info" section. ## 2. Add an iPhone check helper function. We only need to make sure that portrait is available for iPhone, add this simple helper function to your AppDelegate: ```objectivec - (BOOL)isPhone { return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone); } ``` ```swift var isPhone: Bool { return UIDevice.current.userInterfaceIdiom == .phone } ``` ## 3. Add supported orientation function to AppDelegate ### 3.1. Add a boolean flag for allowing orientations Add a boolean property to AppDelegate: ```objectivec @property (nonatomic, assign) BOOL allowAllOrientations; ``` ```swift var allowAllOrientations: Bool = false ``` ### 3.2. Set boolean to NO/false If using Objective-C, inside `application:didFinishLaunchingWithOptions:` as soon as the function is entered set the boolean to NO. If using Swift make this part of the variable definition (as seen in 3.1 above). ### 3.3. Add supported orientations function to AppDelegate ```objectivec - (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { if ([self isPhone]) { if (self.allowAllOrientations) { // App supports all except upside-down return UIInterfaceOrientationMaskAllButUpsideDown; } else { // Initial launch screen supports only Landscape return UIInterfaceOrientationMaskLandscape; } } else { // iPad return UIInterfaceOrientationMaskAll; } } ``` ```swift func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { if isPhone { if allowAllOrientations { return [.allButUpsideDown] } else { return [.landscape] } } else { // iPad return [.all] } } ``` ### 3.4. Set boolean flag to YES When you are ready to allow portrait set the boolean to `YES` (or `true` in Swift). ## 4. LaunchScreen.storyboard If you are using a LaunchScreen.storyboard make sure it is a basic UIViewController without any outlet connections, no view controller subclass is needed for this. ## 5. Using SceneDelegate If you are using a SceneDelegate, you can also return supported orientations per view controller: ```objectivec - (UIInterfaceOrientationMask)supportedInterfaceOrientations { // For example: return UIInterfaceOrientationMaskLandscape; } ``` ```swift override var supportedInterfaceOrientations: UIInterfaceOrientationMask { // For example: return .landscape } ``` You can do this globally and just let your root view controller report available orientations. --- # Android Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android :::tip What you'll do 1. [Install the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#installation) 2. [Set up reward callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#callbacks) 3. [Initialize the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#sdk-initialization) 4. [Display a placement](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#displaying-a-placement) Later sections cover user attributes, the user identifier, placement details, custom parameters, and more. **Testing?** Your app starts in test mode, so you'll need a [test device](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa) to see content while you build. ::: ## Installation See the [Android example apps](https://github.com/TapResearch/demo-apps/tree/master/android) on GitHub. :::note Minimum SDK version: 23. ::: ### Repository * **SDK 3.7.3 and later** are published to [Maven Central](https://central.sonatype.com/artifact/com.tapresearch/tapsdk). Make sure `mavenCentral()` is in your repositories; new Android projects include it by default. * **SDK 3.7.2 and earlier** are published to the [TapResearch Artifactory repository](https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/). Add it to the `dependencyResolutionManagement` repositories in your `settings.gradle` (or `settings.gradle.kts`) file: ```groovy title="settings.gradle" dependencyResolutionManagement { repositories { google() mavenCentral() maven { url "https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/" } } } ``` ```kotlin title="settings.gradle.kts" dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/") } } } ``` ### Dependencies Your app must also declare the libraries the SDK uses — the SDK does not pull them in. Add the following to the `dependencies` block of your app-level `build.gradle` (or `build.gradle.kts`) file: Replace `` with your SDK version from the matching repository above. ```groovy title="build.gradle" implementation 'com.tapresearch:tapsdk:' implementation 'androidx.lifecycle:lifecycle-process:2.6.1' implementation 'androidx.core:core-ktx:1.10.1' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') implementation 'com.google.android.gms:play-services-ads-identifier:18.1.0' implementation 'com.google.android.gms:play-services-appset:16.1.0' ``` ```kotlin title="build.gradle.kts" implementation("com.tapresearch:tapsdk:") implementation("androidx.lifecycle:lifecycle-process:2.6.1") implementation("androidx.core:core-ktx:1.10.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) implementation("com.google.android.gms:play-services-ads-identifier:18.1.0") implementation("com.google.android.gms:play-services-appset:16.1.0") ``` ## Integration ### Callbacks These are the callbacks you provide to the SDK at initialization. | Parameter | Description | Required? | |-------------------------------------------|--------------------------------------------------------------------------------|-----------| | `rewardCallback` (`TRRewardCallback`) | The callback hit every time a reward is received for your users. | No | | `errorCallback` (`TRErrorCallback`) | The error message channel. Used if the SDK experiences an issue. | Yes | | `sdkReadyCallback` (`TRSdkReadyCallback`) | Lets you know when the SDK is done booting. | Yes | | `qqDataCallback` (`TRQQDataCallback`) | The callback hit every time a quick question is answered by your users. | No | A content callback (`TRContentCallback`) is passed when you [show a placement](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#showing-the-placement), not at initialization. It lets you know when content (survey wall, banner, interstitial, etc.) is shown or dismissed — a good time to log analytics. **Reward callback** (`TRRewardCallback`): This is called when the user completes a survey and is passed a list of `TRReward` objects. Pass a reward callback to `initialize` to receive in-app rewards. **Error callback** (`TRErrorCallback`): This is called when there is an error and is passed an instance of `TRError`. **sdkReadyCallback** (`TRSdkReadyCallback`): This is called when the SDK is ready to show content. This is a good time to show content. ### SDK initialization You will need to initialize the SDK with both a unique user identifier and an api token. We suggest initializing the TapResearch SDK as early in the boot process as possible (once the user identifier is available). The demo app initializes in the `onCreate` method of the `MainActivity`. Similarly, there is a callback for when the SDK is ready. This is called when the SDK has finished initializing and is ready to show content. We suggest showing content only when the SDK is ready. Optionally, you may pass an initOptions parameter through initialization for additional configuration. See example below and [Initialization Options](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/initialization-options). :::info If user attributes are known at SDK initialization, it is preferable to pass them through `initOptions` compared to using `sendUserAttributes`. This will result in quicker load times for targeted content. ::: :::tip The user identifier passed through initialization must be **unique**! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service. ::: ```kotlin title="MainActivity.kt" val myUserIdentifier = "public-demo-test-user" val myApiToken = getString(R.string.api_token) TapResearch.initialize( apiToken = myApiToken, // userIdentifier must be unique for your player! userIdentifier = myUserIdentifier, context = this@MainActivity, // optional, handle rewards here or pass null and call setRewardCallback to handle later rewardCallback = { rewards -> showRewardToast(rewards) }, errorCallback = { error -> showErrorToast(error) }, sdkReadyCallback = { Log.d(LOG_TAG, "Tap SDK initialized and ready") }, // optional, handle here or pass null and call setQuickQuestionCallback to handle later qqDataCallback = { quickQuestionPayload -> Log.d(LOG_TAG, "Quick Question completed payload: $quickQuestionPayload") }, // optional, can pass null initOptions = TapInitOptions( userAttributes = hashMapOf( "is_vip" to 1, "something_else" to 2, ), clearPreviousAttributes = false, ), ) ``` ```java title="MainActivity.java" String myUserIdentifier = "public-demo-test-user"; String myApiToken = getString(R.string.api_token); HashMap userAttributes = new HashMap<>(); userAttributes.put("is_vip", 1); userAttributes.put("something_else", 2); TapResearch.INSTANCE.initialize( myApiToken, myUserIdentifier, // user identifier must be unique for your player! MainActivity.this, // optional, handle rewards here or pass null and call setRewardCallback to handle later (rewards) -> { showRewardToast(rewards); }, (trError) -> { showErrorToast(trError); }, () -> { Log.d(LOG_TAG, "SDK is ready"); // now that the SDK is ready, we can show content and send user attributes sendUserAttributes(); // showContent(); }, // optional quick question completed handler, can pass null here and call setQuickQuestionCallback later (quickQuestionPayload) -> { Log.d(LOG_TAG, "Quick Question was completed. Payload: " + quickQuestionPayload); }, // optional, can pass in options or null new TapInitOptions( userAttributes, false // true replaces previously sent user attributes ) ); ``` ### Checking if SDK is ready to use In addition to waiting for the `sdkReadyCallback` via `initialize` (which is our recommended method), you can also determine if the SDK is ready to use by checking `isReady` ```kotlin if (TapResearch.isReady()) { // ... } else { // ... } ``` ```java if (TapResearch.INSTANCE.isReady()) { // ... } else { // ... } ``` #### Reward callback setup additional information The **reward callback** can be set in two ways; via `initialize` and also via `setRewardCallback`. It is required for apps using in-app callbacks for rewards. Here we are just defining something that you are able to do with the rewards that are returned. ```kotlin title="MainActivity.kt" // this can be set via initialize and also via setRewardCallback method val rewardCallback = TRRewardCallback { rewards -> showRewardToast(rewards) } TapResearch.setRewardCallback(rewardCallback) private fun showRewardToast(rewards: MutableList) { var rewardCount = 0f for (reward: TRReward in rewards) { reward.rewardAmount?.let { rewardCount += it } } val currencyName = rewards.first().currencyName val eventType = rewards.first().payoutEventType Toast.makeText( this@MainActivity, "Congrats! You've earned $rewardCount $currencyName. Event type is $eventType", Toast.LENGTH_LONG, ).show() } // Note: in your Activity.onPause(), do: TapResearch.setRewardCallback(null) // Similarly, in your Activity.onResume(), do: TapResearch.setRewardCallback(rewardCallback) ``` ```java title="MainActivity.java" TRRewardCallback rewardCallback = rewards -> showRewardToast(rewards); TapResearch.INSTANCE.setRewardCallback(rewardCallback); private void showRewardToast(List rewards) { float rewardCount = 0; for (TRReward reward : rewards) { if (reward.getRewardAmount() != null) { rewardCount += reward.getRewardAmount(); } } String currencyName = rewards.isEmpty() ? "" : rewards.get(0).getCurrencyName(); String eventType = rewards.isEmpty() ? "" : rewards.get(0).getPayoutEventType(); Toast.makeText(MainActivity.this, "Congrats! You've earned " + rewardCount + " " + currencyName + ". Event type is " + eventType, Toast.LENGTH_LONG).show(); } // Note: in your Activity.onPause(), do: TapResearch.INSTANCE.setRewardCallback(null); // Similarly, in your Activity.onResume(), do: TapResearch.INSTANCE.setRewardCallback(rewardCallback); ``` #### Reward object Each reward is a `TRReward`. See [TRReward](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward) for its properties. :::note In Java, use the getters: `getRewardAmount()`, `getCurrencyName()`, `getPayoutEventType()`, and so on. ::: A TRReward object looks like this when printed: ```text TRReward(transactionIdentifier=tap_pr_062ff34493dff7123691f79921978391, placementIdentifier=ffbd56740417114bc70c57f93151fa74, currencyName=tokens, rewardAmount=10.0, payoutEventType=PROFILE_REWARD, placementTag=normal-offer) ``` #### Quick Question callback setup additional information New to Quick Questions? See [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions) for what they are and the response payload. The **quick question callback** can be set in two ways; via `initialize` and also via `setQuickQuestionCallback`. It is optional for apps using quick questions that want to receive additional information when a user completes a quick question. Here we are just defining something that you are able to do with the quick question data that is returned. ```kotlin title="MainActivity.kt" // this can be set via initialize and also via setQuickQuestionCallback method val quickQuestionCallback = TRQQDataCallback { quickQuestionPayload -> Log.d(LOG_TAG, "Quick Question payload data: $quickQuestionPayload") } TapResearch.setQuickQuestionCallback(quickQuestionCallback) // important: when you no longer need to wait for quick question data, such as Activity onPause, do: TapResearch.setQuickQuestionCallback(null) ``` ```java title="MainActivity.java" // this can be set via initialize and also via setQuickQuestionCallback method TRQQDataCallback quickQuestionCallback = new TRQQDataCallback() { @Override public void onQuickQuestionDataReceived(QQPayload qqPayload) { Log.d(LOG_TAG, "quick question data received: " + qqPayload); } }; TapResearch.INSTANCE.setQuickQuestionCallback(quickQuestionCallback); // important: when you no longer need to wait for quick question data, such as Activity onPause, do: TapResearch.INSTANCE.setQuickQuestionCallback(null); ``` #### Quick Question data payload object A QQPayload object will look like this: ```kotlin data class QQPayload( val surveyIdentifier: String, val appName: String, val apiToken: String, val sdkVersion: String, val platform: String, val placementTag: String, val userIdentifier: String, val userLocale: String, val seenAt: String, val complete: Complete?, val questions: List, val targetAudience: Array>?, ) ``` #### Error callbacks You can use the error callback to log or report errors. :::info See the [Error Codes](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/sdk-error-codes) page for more information on the error codes. ::: ```kotlin title="MainActivity.kt" // This is set in the initialization callback errorCallback = { trError -> showErrorToast(trError) } ``` ...and the error toast ```kotlin title="MainActivity.kt" private fun showErrorToast(trError: TRError) { Toast.makeText( this@MainActivity, "Error: ${trError.description}", Toast.LENGTH_LONG, ).show() } ``` ```java title="MainActivity.java" // This is set in the error callback new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { showErrorToast(trError); } } ``` ...and the error toast ```java title="MainActivity.java" private void showErrorToast(TRError trError) { Toast.makeText(MainActivity.this, "Error: " + trError.getDescription(), Toast.LENGTH_LONG).show(); } ``` #### TRError object The error object contains the following properties (both nullable). See [TRError](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tap-objects) for details. * `code`: The error code. This can be used when debugging as there is an associated list of error codes and descriptions. * `description`: The error message. This is a human readable description of the error. ### Send user attributes :::info See [User attributes and targeting](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes) for more. ::: User attributes are used to target specific users with content (ex: an offer). The most recent values received are used when determining which content to show. Pass user attributes in `initOptions` during initialization, or call `sendUserAttributes` after the SDK is ready. Attribute names must be strings and must not begin with `tapresearch_`, which is reserved for TapResearch. Use string or numeric values for targeting. For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. Set `clearPreviousAttributes` to `false` to merge new values with existing attributes, or `true` to replace the attributes you previously sent. ```kotlin initialize( // ... initOptions = TapInitOptions( userAttributes = hashMapOf( "is_vip" to 1, "something_else" to 2, ), clearPreviousAttributes = true, ), //... ) ``` To update attributes after initialization, pass a `HashMap` to `sendUserAttributes`. We suggest sending as many attributes as you think that you'll want to target on for surveys, special awards, etc. ```kotlin title="MainActivity.kt" initialize( // ... sdkReadyCallback = object : TRSdkReadyCallback { override fun onTapResearchSdkReady() { Log.d(LOG_TAG, "SDK is ready") val userAttributes: HashMap = HashMap() userAttributes["age"] = 25 userAttributes["VIP"] = 1 userAttributes["name"] = "John Doe" userAttributes["first_seen"] = Instant.now().toString() TapResearch.sendUserAttributes( userAttributes, ) { trError -> showErrorToast(trError) } //... } }, //... ) ``` ```java title="MainActivity.java" initialize( //... public void onTapResearchSdkReady() { Log.d(LOG_TAG, "SDK is ready"); HashMap userAttributes = new HashMap<>(); userAttributes.put("age", 25); userAttributes.put("VIP", 1); userAttributes.put("name", "John Doe"); userAttributes.put("first_seen", Instant.now().toString()); // Java must pass every argument: attributes, clearPreviousAttributes, error callback TapResearch.INSTANCE.sendUserAttributes(userAttributes, false, new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { Log.d(LOG_TAG, "Error sending user attributes: " + trError.toString()); } }); //... } //... ) ``` ### Setting the user identifier You can set the user identifier at any time by calling `setUserIdentifier` with a string. ```kotlin TapResearch.setUserIdentifier(userId) ``` ```java TapResearch.INSTANCE.setUserIdentifier(userIdentifier); ``` ### Displaying a placement #### Checking if the placement is available A placement is a location in your app where TapResearch content is displayed to the user. Ideally, you check to see if the placement can be shown before actually showing it. To find out if the content is available for the given user, call `canShowContentForPlacement("placement tag", errorCallback)`. The error callback is required; it receives the reason when the placement can't be shown. ```kotlin title="MainActivity.kt" if (TapResearch.canShowContentForPlacement( placementTag, errorCallback = object : TRErrorCallback { override fun onTapResearchDidError(trError: TRError) { trError.description?.let { Log.e("TRERROR", it) } } }, ) ) { //... } ``` ```java title="MainActivity.java" if (TapResearch.INSTANCE.canShowContentForPlacement(placementTag, new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { Log.d("TRLOG", "Whoops " + trError.toString()); } })) { //... } ``` #### Showing the placement The placement is shown by calling `showContentForPlacement`. Its parameters, in order, are the placement tag, an optional `TRContentCallback`, optional custom parameters (a `HashMap`), and a required `TRErrorCallback`. The content callback receives the placement tag (a `String`) in `onTapResearchContentShown` and `onTapResearchContentDismissed`. ```kotlin title="MainActivity.kt" TapResearch.showContentForPlacement( tag = placementTag, contentCallback = object : TRContentCallback { override fun onTapResearchContentShown(placementTag: String) { tapResearchContentShown(placementTag) } override fun onTapResearchContentDismissed(placementTag: String) { tapResearchDidDismiss(placementTag) } }, customParameters = customParameters, errorCallback = object : TRErrorCallback { override fun onTapResearchDidError(trError: TRError) { showErrorToast(trError) } }, ) ``` ```java title="MainActivity.java" TapResearch.INSTANCE.showContentForPlacement( placementTag, new TRContentCallback() { @Override public void onTapResearchContentShown(String placementTag) { Log.d("TRLOG", "Content shown for placement " + placementTag); } @Override public void onTapResearchContentDismissed(String placementTag) { Log.d("TRLOG", "Content dismissed for placement " + placementTag); } }, customParameters, // or null new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { showErrorToast(trError); } } ); ``` ### Passing custom parameters Custom parameters allow you to receive additional information about the user when they complete a survey. This can be used to filter out users who have already completed a survey, or to target specific users. These params will be returned in your [server-to-server postbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#server-to-server-callbacks). There are a maximum of 5 custom parameters that can be passed per placement. ```kotlin title="MainActivity.kt" val customParameters: HashMap = HashMap() customParameters["age"] = 25 customParameters["VIP"] = 1 customParameters["name"] = "John Doe" TapResearch.showContentForPlacement( tag = placementTag, contentCallback = object : TRContentCallback { override fun onTapResearchContentShown(placementTag: String) { tapResearchContentShown(placementTag) } override fun onTapResearchContentDismissed(placementTag: String) { tapResearchDidDismiss(placementTag) } }, customParameters = customParameters, errorCallback = object : TRErrorCallback { override fun onTapResearchDidError(trError: TRError) { showErrorToast(trError) } }, ) ``` ```java title="MainActivity.java" HashMap customParameters = new HashMap<>(); customParameters.put("age", 25); customParameters.put("VIP", 1); customParameters.put("name", "John Doe"); TapResearch.INSTANCE.showContentForPlacement( placementTag, new TRContentCallback() { @Override public void onTapResearchContentShown(String placementTag) { Log.d("TRLOG", "Content shown for placement " + placementTag); } @Override public void onTapResearchContentDismissed(String placementTag) { Log.d("TRLOG", "Content dismissed for placement " + placementTag); } }, customParameters, new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { showErrorToast(trError); } } ); ``` ### Getting placement details :::info `getPlacementDetails` is available from v3.6.3 ::: :::warning Don't cache placement details Placement details can change as our SDK refreshes placements in the background, so it is not recommended to store or cache these values. Instead, refetch the details each time you need to use them, e.g. to display something in your UI. ::: Get placement details by calling `getPlacementDetails("placement tag", optional errorCallback)`. It will return a TRPlacementDetails object or null: ```kotlin data class TRPlacementDetails( val name: String? = null, val contentType: String? = null, val currencyName: String? = null, val isSale: Boolean? = null, val saleType: String? = null, val saleEndDate: String? = null, // UTC date string. i.e. SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") val saleMultiplier: Float? = null, val saleDisplayName: String? = null, val saleTag: String? = null, val bonusBarProgress: TRBonusBarProgress? = null, ) data class TRBonusBarProgress( val isActive: Boolean? = null, val currentCompletes: Int? = null, val bonusWindowEndAt: String? = null, // UTC date string. i.e. SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") val bonusTiers: List? = null, val error: TRError? = null, ) data class TRBonusTier( val tierNumber: Int? = null, val completesNeeded: Int? = null, val rewardAmount: Float? = null, val status: String? = null, ) ``` ```kotlin title="MainActivity.kt" val placementDetails = TapResearch.getPlacementDetails("my-placement-tag") { error -> println(error) } ``` ```java title="MainActivity.java" TRPlacementDetails placementDetails = TapResearch.INSTANCE.getPlacementDetails( "my-placement-tag", new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { Log.d("Error", "Error getting placement details " + trError.toString()); } } ); ``` ### Granting boosts Call `grantBoost` with a configured [User Boosts](https://supply-docs.tapresearch.com/docs/3.x/features/user-boosts) tag. The result callback is optional: ```kotlin title="MainActivity.kt" import kotlinx.serialization.InternalSerializationApi @OptIn(InternalSerializationApi::class) fun grantBoost() { TapResearch.grantBoost("BOOST_TAG") { response -> if (response.success == true) { // Boost granted } else { response.error?.let { Log.e(LOG_TAG, "Grant boost failed: ${it.code} ${it.description}") } } } } ``` ```java title="MainActivity.java" TapResearch.INSTANCE.grantBoost("BOOST_TAG", response -> { if (Boolean.TRUE.equals(response.getSuccess())) { // Boost granted } else if (response.getError() != null) { Log.e(LOG_TAG, "Grant boost failed: " + response.getError().getCode() + " " + response.getError().getDescription()); } }); ``` ### Android ProGuard Add these rules to your app's ProGuard/R8 rules file: ```text -keep class com.tapresearch.** { *; } -dontwarn com.tapresearch.** -keepattributes Exceptions, InnerClasses, Signature, Deprecated, SourceFile, LineNumberTable, *Annotation*, EnclosingMethod, MethodParameters, LocalVariableTable, LocalVariableTypeTable -keepparameternames -keepclassmembers class * { @android.webkit.JavascriptInterface ; } ``` ## Need help? If you're experiencing issues or need assistance, reach out to [developers@tapresearch.com](mailto:developers@tapresearch.com). --- **Stay updated:** watch [TapResearch Android SDK releases](https://github.com/TapResearch/TapResearch-Android-SDK/releases) on GitHub to get notified about new SDK releases. --- # Android Native Survey Tiles Source: https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/android-surveys :::info Test mode If your app is in test mode, you **must** use a test user. Add test users in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under **More → Test devices**. ::: ## Installation * **Integration guide:** See the [Android SDK documentation](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android) * **Example apps:** Available on [GitHub](https://github.com/TapResearch/demo-apps/tree/master/android) ### Before you begin * You must be on SDK v3.4.0+ to have access to Native Survey Tiles. * The wall preview feature needs to be enabled and set up by the TapResearch team so please reach out to your account manager when you are ready to get started * Native Survey Tiles should be on a dedicated placement, separate from the standard Survey Wall (if applicable) * **Always wait** until the SDK is ready (via the `onTapResearchSdkReady` callback or by checking `TapResearch.isReady()`) before calling any Native Survey Tiles interfaces :::info Support Any other questions? Reach out to your account manager or via Slack and we'll be happy to help! ::: ## Introduction **Native Survey Tiles** (formerly *Survey Wall Preview*) let you provide users with available surveys, directly inside your app. Some benefits of this include: * **Custom UI** — Create your own UI presentation with survey tiles/cards * **Direct survey access** — Provide users with direct entry into surveys they choose --- ### Key considerations & UI design When designing your survey tiles/cards, consider these elements: ### 1. The profile survey New users must complete the introductory profile survey before they can access other surveys. For these new users, the SDK will return **only** this survey until they have completed it. * Only shown to users that are new to TapResearch * Will always be returned by itself, with a survey identifier of `profile` * Display example: ### 2. Survey details After completing the profile survey, users should then start receiving real surveys. Surveys will have the following attributes: ```kotlin data class TRSurvey( // Primary attributes val surveyId: String? = null, // Unique identifier, will be used when a user taps on a survey val lengthInMinutes: Int? = null, // The estimated time to complete the survey val rewardAmount: Float? = null, // The reward amount for taking the survey val currencyName: String? = null, // The name of the reward currency val isHotTile: Boolean? = null, // Whether the survey should be designated as a "hot tile" val category: String? = null, // The survey's category, if it has one // Sale attributes var isSale: Boolean? = null, // Whether a currency sale is currently active var saleEndDate: String? = null, // If a sale is active, the UTC date it will end var saleMultiplier: Float? = null, // The multiplier being applied to the sale var preSaleRewardAmount: Float? = null, // The pre-multiplier reward amount, for enhancing UI during a sale ) ``` * We recommend displaying all primary data points to allow users to choose which survey length/reward they want * Display example: ### 3. "Hot" tiles Hot tiles are surveys with exceptional scores, typically offering the best time-to-reward ratio. Displaying a special UI highlight for these surveys is recommended, as they tend to increase user engagement and click-through rates. * Display example: ### 4. Currency Sales When running promotional currency sales, the relevant sale attributes of the survey object will populate, allowing you to show enhanced UIs and CTAs to users. We recommend using this data to display the extra rewards boost on the individual tiles. * Display example: --- ## Integration The code below demonstrates how to: 1. Fetch surveys after SDK initialization 2. Set up a surveys refreshed listener **Key points:** * Fetch surveys **only after** the SDK Ready event * Use `hasSurveysForPlacement(placementTag, errorListener)` if you only need to know whether any surveys are available * Render the `TRSurvey` objects in your UI (see `handleMySurveys` function) * Set the surveys refreshed listener to get updates when surveys change :::warning Attempting to fetch surveys **before** SDK Ready will result in an error and no surveys will be returned. ::: ```kotlin TapResearch.initialize( apiToken = "my-api-token", userIdentifier = "my-user-identifier", context = this@MainActivity, errorCallback = { error -> handleMyError(error) }, sdkReadyCallback = { Log.d(LOG_TAG, "SDK is ready") // Surveys can be fetched anytime after SDK Ready val surveys = TapResearch.getSurveysForPlacement("my-placement-tag") { error -> handleMyError(error) } handleMySurveys(surveys) // your surveys handler; handle null or empty list // Set your surveys refreshed listener TapResearch.setSurveysRefreshedListener(object : TRSurveysRefreshedListener { override fun onSurveysRefreshedForPlacement(placementTag: String) { if (placementTag == "my-placement-tag") { val updatedSurveys = TapResearch.getSurveysForPlacement(placementTag) { error -> handleMyError(error) } handleMySurveys(updatedSurveys) // your surveys handler; handle null or empty list } } }) }, rewardCallback = { rewards -> handleMyRewards(rewards) }, initOptions = null, qqDataCallback = null, ) ``` ```java TapResearch.INSTANCE.initialize( "my-api-token", "my-user-identifier", MainActivity.this, // context this::handleMyRewards, this::handleMyError, // SDK ready handler () -> { Log.d(LOG_TAG, "SDK Ready"); // Surveys can be fetched anytime after SDK Ready List surveys = TapResearch.INSTANCE.getSurveysForPlacement("my-placement-tag", this::handleMyError); handleMySurveys(surveys); // your surveys handler; handle null or empty list // Set your surveys refreshed listener TapResearch.INSTANCE.setSurveysRefreshedListener(placementTag -> { if (placementTag.equals("my-placement-tag")) { List updatedSurveys = TapResearch.INSTANCE.getSurveysForPlacement(placementTag, this::handleMyError); handleMySurveys(updatedSurveys); } }); }, this::handleQuickQuestion, null //tapInitOptions ); ``` ### Showing a survey You will need the **surveyId** from the **TRSurvey** object in order to show the survey: ```kotlin val surveyId = survey.surveyId ?: return // from TRSurvey object TapResearch.showSurveyForPlacement( placementTag = "my-placement-tag", surveyId = surveyId, customParameters = null, contentListener = object : TRContentCallback { override fun onTapResearchContentShown(placementTag: String) { // survey is being shown } override fun onTapResearchContentDismissed(placementTag: String) { // survey was dismissed } }, errorListener = object : TRErrorCallback { override fun onTapResearchDidError(trError: TRError) { // a rare error occurred while showing the survey } } ) ``` ```java String surveyId = survey.getSurveyId(); // from TRSurvey object; check for null TapResearch.INSTANCE.showSurveyForPlacement( "my-placement-tag", surveyId, null, // custom parameters new TRContentCallback() { @Override public void onTapResearchContentShown(String placementTag) { // survey is being shown } @Override public void onTapResearchContentDismissed(String placementTag) { // survey was dismissed } }, new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { // a rare error occurred while showing the survey } } ); ``` ### Passing custom parameters If you need to pass custom parameters, please use: ```kotlin val customParameters = HashMap() customParameters["isVIP"] = 1 TapResearch.showSurveyForPlacement( placementTag = "my-placement-tag", surveyId = surveyId, // the non-null surveyId from your TRSurvey object customParameters = customParameters, contentListener = object : TRContentCallback { override fun onTapResearchContentShown(placementTag: String) { // survey is being shown } override fun onTapResearchContentDismissed(placementTag: String) { // survey has been dismissed } }, errorListener = object : TRErrorCallback { override fun onTapResearchDidError(trError: TRError) { // rare error occurred while showing the survey } } ) ``` ```java HashMap customParameters = new HashMap<>(); customParameters.put("isVIP", 1); TapResearch.INSTANCE.showSurveyForPlacement( "my-placement-tag", survey.getSurveyId(), // from your TRSurvey object; check for null customParameters, new TRContentCallback() { @Override public void onTapResearchContentShown(String placementTag) { // survey is being shown } @Override public void onTapResearchContentDismissed(String placementTag) { // survey has been dismissed } }, new TRErrorCallback() { @Override public void onTapResearchDidError(TRError trError) { // a rare error occurred while showing the survey } } ); ``` --- # TRError Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tap-objects **Platform note:** This object is only available on Android. iOS uses `NSError` objects for errors (see [iOS](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tap-objects#ios-nserror) below). ## Properties * **`code`** (Int?) — The error code. See [SDK Error Codes](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/sdk-error-codes) for the full list. * **`description`** (String?) — The error message. In Java, use `getCode()` and `getDescription()`. ## iOS: NSError On iOS, errors are `NSError` objects: * **`code`** (Int) — The error code. See [SDK Error Codes](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/sdk-error-codes). * **`localizedDescription`** (String) — The error message. --- # React Native Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native :::tip What you'll do 1. [Install the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#installation) 2. [Initialize the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#initialization) 3. [Set up callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#sdk-callbacks-setup) 4. [Display a placement](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#displaying-a-placement) Later sections cover rewards, user attributes, the user identifier, Quick Questions, boosts, and placement details. **Testing?** Your app starts in test mode, so you'll need a [test device](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa) to see content while you build. ::: ## Installation :::warning targetSdk 35+ If your app's `targetSdk` is 35 or higher, use `react-native-tapresearch` 3.5.3 or higher — Android 15 introduced a change that otherwise cuts off the top title bar of the Survey Wall. ::: Replace `` below with your SDK version from [npm](https://www.npmjs.com/package/react-native-tapresearch?activeTab=versions). Run the following command: ```bash yarn add react-native-tapresearch@ ``` Install the iOS pods: ```bash cd ios && pod install ``` * **3.7.x and 3.8.0:** The TapResearch iOS framework ships with `react-native-tapresearch` and is linked automatically, so no manual pod entry is needed. * **3.8.1 and later:** CocoaPods adds the TapResearch iOS SDK as a Swift Package. Requires **React Native 0.75 or later** and iOS 15.0 or later. No manual pod or package entry is needed. * **3.7.x:** In build.gradle of your app, add `maven { url "https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/" }` in the `allprojects/repositories` section: ```groovy allprojects { repositories { ... maven { url "https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/" } ... } } ``` * **3.8 and later:** The TapResearch Android SDK is published to Maven Central, so no extra repository is needed as long as `mavenCentral()` is in your repositories. ## Integration ### Import the TapResearchSDK components ```typescript import { PlacementCustomParamsProps, TapSdkAdapter, TapSdkUI, TapUserAttributes, TapSdkReward, TapSdkError, TRQQDataPayload, TapSdkGrantBoostResponse, TapSdkPlacementDetails, } from 'react-native-tapresearch'; ``` ### Initialization Initialize the TapResearchSDK as early as possible so TapResearch can start getting surveys ready for your users. Call `initializeSdk()` once, after `` has mounted and registered its callbacks. Use the API token of the matching app — your iOS and Android apps have different API tokens. :::tip The user identifier passed through initialization must be **unique**! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service. ::: #### Parameters - apiToken - your app's API token, from its **Settings** tab in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) - userIdentifier - unique identifier assigned to the current user of your app - userAttributes - optional; map with string keys and string or number values, used for targeting content - clearPreviousAttributes - optional boolean, default `false`; remove all user attributes previously assigned to userIdentifier ```typescript // export type TapUserAttributes = Record; // TapSdkAdapter.initializeSdk(apiToken: string, userIdentifier: string, userAttributes?: TapUserAttributes | null, clearPreviousAttributes = false) TapSdkAdapter.initializeSdk("YOUR_IOS_OR_ANDROID_API_TOKEN", "USER_IDENTIFIER"); // Or, with user attributes known at initialization: TapSdkAdapter.initializeSdk("YOUR_IOS_OR_ANDROID_API_TOKEN", "USER_IDENTIFIER", userAttributes, false); ``` ### SDK callbacks setup The SDK will call delegate functions to notify the app of certain events, for example to pass rewards or to notify the SDK has taken over the UI. Define the callback functions with the following signatures. The first four are required props of ``; the rest are optional: ```typescript onTapResearchSdkReady: () => void; onDidReceiveError: (error: TapSdkError) => void; onContentShown: (placementTag: string) => void; onContentDismissed: (placementTag: string) => void; onDidReceiveRewards?: (rewards: Array) => void; onDidReceiveQQResponse?: (qqResponse: TRQQDataPayload) => void; onSurveysRefreshedForPlacement?: (placementTag: string) => void; onGrantBoostResponse?: (boostResponse: TapSdkGrantBoostResponse) => void; ``` Errors are passed as a `TapSdkError`: ```typescript export type TapSdkError = { error_code: number; message: string; }; ``` For example, a reward callback may look like this: ```typescript function doRewards(rewards: Array) { console.log('Received some rewards', rewards); } ``` Make sure to render the TapSDK component with the previously defined callbacks as its properties: ```tsx ``` :::info Keep TapSdkUI mounted Keep `` mounted with the callbacks you need, for example in your root component. ::: ### Checking if the SDK is ready As mentioned above, you can determine when the SDK is ready via the onTapResearchSdkReady callback. Alternatively, you can also call the isReady method: ```typescript TapSdkAdapter.isReady() ``` It may look something like this (example in Typescript): ```tsx const isReady = useCallback(() => { TapSdkAdapter.isReady() .then((result: any) => { console.log(`TapSdkAdapter.isReady() - Result: ${result}`); Toast.hide(); Toast.show({ type: result ? 'success' : 'error', text1: 'TapSdkAdapter.isReady?', text2: result.toString(), }); }) .catch((error: any) => { console.error(error); }); }, []); ``` ```tsx ``` ```tsx const getPlacementDetails = useCallback(() => { TapSdkAdapter.getPlacementDetails('earn-center') .then((result: TapSdkPlacementDetails | null) => { if (result != null) { alert( "Placement Details: " + result.name + " currency_name: " + result.currency_name + " is_sale: " + result.is_sale + " sale_type: " + result.sale_type + " sale_multiplier: " + result.sale_multiplier + " sale_display_name: " + result.sale_display_name + " sale_tag: " + result.sale_tag + " content_type: " + result.content_type + " sale_end_date: " + result.sale_end_date ); } }) .catch((error: any) => { console.error(error); }); }, []); ``` ## Need help? Email [developers@tapresearch.com](mailto:developers@tapresearch.com). **Stay updated:** see [react-native-tapresearch on npm](https://www.npmjs.com/package/react-native-tapresearch) for new releases. --- # React Native Native Survey Tiles Source: https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/react-native-surveys :::info Test mode If your app is in test mode, you **must** use a test user. Add test users in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under **More → Test devices**. ::: ## Installation See latest versions at https://www.npmjs.com/package/react-native-tapresearch Start with the [React Native integration guide](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native) if you haven't set up the SDK yet. ### Before you begin * You must be on SDK v3.4.1+ to have access to Native Survey Tiles. * The wall preview feature needs to be enabled and set up by the TapResearch team so please reach out to your account manager when you are ready to get started * Native Survey Tiles should be on a dedicated placement, separate from the standard Survey Wall (if applicable) * **Always wait** until the SDK is ready (via the `onTapResearchSdkReady` callback or by checking `TapSdkAdapter.isReady()`) before calling any Native Survey Tiles interfaces :::info Support Any other questions? Reach out to your account manager or via Slack and we'll be happy to help! ::: ## Introduction **Native Survey Tiles** (formerly *Survey Wall Preview*) let you provide users with available surveys, directly inside your app. Some benefits of this include: * **Custom UI** — Create your own UI presentation with survey tiles/cards * **Direct survey access** — Provide users with direct entry into surveys they choose --- ### Key considerations & UI design When designing your survey tiles/cards, consider these elements: ### 1. The profile survey New users must complete the introductory profile survey before they can access other surveys. For these new users, the SDK will return **only** this survey until they have completed it. * Only shown to users that are new to TapResearch * Will always be returned by itself, with a survey identifier of `profile` * Display example: ### 2. Survey details After completing the profile survey, users should then start receiving real surveys. Surveys will have the following attributes: ```typescript type TapSdkSurvey = { // Primary attributes survey_identifier: string; // Unique identifier, will be used when a user taps on a survey length_in_minutes: number; // The estimated time to complete the survey reward_amount: number; // The reward amount for taking the survey currency_name: string; // The name of the reward currency is_hot_tile: boolean; // Whether the survey should be designated as a "hot tile" category: string | null; // The survey's category, if it has one // Sale attributes is_sale: boolean; // Whether a currency sale is currently active sale_end_date: string | null; // If a sale is active, the UTC date it will end sale_multiplier: number; // The multiplier being applied to the sale pre_sale_reward_amount: number; // The pre-multiplier reward amount, for enhancing UI during a sale } ``` * We recommend displaying all primary data points to allow users to choose which survey length/reward they want * Display example: ### 3. "Hot" tiles Hot tiles are surveys with exceptional scores, typically offering the best time-to-reward ratio. Displaying a special UI highlight for these surveys is recommended, as they tend to increase user engagement and click-through rates. * Display example: ### 4. Currency Sales When running promotional currency sales, the relevant sale attributes of the survey object will populate, allowing you to show enhanced UIs and CTAs to users. We recommend using this data to display the extra rewards boost on the individual tiles. * Display example: --- ## Integration ### Importing SDK components ```typescript import { TapSdkAdapter, TapSdkUI, TapSdkSurvey, } from 'react-native-tapresearch'; ``` ### `hasSurveysForPlacement` Once the SDK is ready (`onTapResearchSdkReady` callback received), you can check if there are available surveys for a given placement tag: ```typescript /** * hasSurveysForPlacement(placementTag: string): Promise */ const hasSurveys = await TapSdkAdapter.hasSurveysForPlacement('wall-preview-placement'); ``` ### `getSurveysForPlacement` Returns the available surveys for the placement, if any. ```typescript /** * getSurveysForPlacement(placementTag: string): Promise */ const surveys = await TapSdkAdapter.getSurveysForPlacement('wall-preview-placement'); ``` ### `onSurveysRefreshedForPlacement` The SDK uses callback functions to notify apps of important events. In React Native, these callbacks are passed as props through the `` component. **Required:** When a placement refreshes with new surveys, `onSurveysRefreshedForPlacement` will be triggered so you can update your native UI. Configuring this callback properly is critical to ensuring users have up to date survey information and a good user experience, as survey inventory/availability is constantly changing. **SDK Callback Signatures** ```typescript onSurveysRefreshedForPlacement?: (placementTag: string) => void; onDidReceiveRewards?: (rewards: Array) => void; onDidReceiveError: (error: TapSdkError) => void; onContentShown: (placementTag: string) => void; onContentDismissed: (placementTag: string) => void; onTapResearchSdkReady: () => void; onDidReceiveQQResponse?: (qqResponse: TRQQDataPayload) => void; onGrantBoostResponse?: (boostResponse: TapSdkGrantBoostResponse) => void; ``` Register this callback on `` as described in [SDK callback setup](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#sdk-callbacks-setup). The `onSurveysRefreshedForPlacement` setup might look like this: ```tsx const onSurveysRefreshed = useCallback(async (placementTag: string) => { try { const newSurveys = await TapSdkAdapter.getSurveysForPlacement(placementTag); actions.setSurveys(newSurveys ?? []); // update UI with new surveys } catch {} }, []); // .. etc return ( ) ``` ### `showSurveyForPlacement` When a user taps on a survey tile, you will call `showSurveyForPlacement` using the appropriate placement tag and `survey_identifier` for that tile. The user will then be brought directly into the survey. You can optionally pass custom parameters as the third argument, as with `showContentForPlacement`. ```tsx /** * showSurveyForPlacement(placementTag: string, surveyId: string, placementCustomParams?: PlacementCustomParamsProps): void */ const onTilePress = (surveyId: string) => { TapSdkAdapter.showSurveyForPlacement('wall-preview-placement', surveyId); }; return ( onTilePress(survey.survey_identifier)} /> ) ``` --- # Unity Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity :::tip What you'll do 1. [Install the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#installation) 2. [Set up reward callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#callbacks) 3. [Initialize the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#initialization) 4. [Display a placement](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#displaying-a-placement) Later sections cover platform setup, user attributes, the user identifier, and more. **Testing?** Your app starts in test mode, so you'll need a [test device](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa) to see content while you build. ::: ## Installation Download **Source code (zip)** for your SDK version from [GitHub releases](https://github.com/TapResearch/TapResearch-Unity-SDK/releases), then extract the archive. You can follow our simple C# script [TapResearchWallDemo.cs](https://github.com/TapResearch/demo-apps/blob/master/unity/WallUnityExample/Assets/TapResearchWallDemo/TapResearchWallDemo.cs) as an example or proceed with the steps below. ### External Dependency Manager Before importing **TapResearchSDK.unitypackage**, install an External Dependency Manager (EDM): * **Unity 2022.3 and later:** install Unity's [External Dependency Manager](https://docs.unity3d.com/Packages/com.unity.external-dependency-manager@2.1/manual/get-started-with-edm.html) from the Package Manager. * **Older Unity versions:** download Google's [external-dependency-manager-latest.unitypackage](https://github.com/googlesamples/unity-jar-resolver/blob/master/external-dependency-manager-latest.unitypackage) and import it with **Assets -> Import Package -> Custom Package**. :::note If you already have EDM installed, skip this step or use this opportunity to update to the latest EDM. ::: Import all files :::info **Previously Installed Android?** If you have previously integrated for Android, please **remove** any existing TapResearch aar files before importing the new package. **Assets/Plugins/Android/tapsdk.aar** **Assets/Plugins/Android/*unitybridge.aar** **Assets/TapResearchSDK/Plugins/Android/tapsdk.aar****Assets/TapResearchSDK/Plugins/Android/*unitybridge.aar** ::: ### Integrate .unitypackage The extracted folder contains **TapResearchSDK.unitypackage**. You can import the package by selecting **Assets > Import Package > Custom Package...** through the Unity menu. The TapResearch SDK includes the binaries for iOS and uses EDM to fetch Android binaries. ### Android setup :::warning targetSdk 35+ If your app's targetSdk is 35 or higher, you will need to use version 3.5.3 or higher. Android 15 introduced a change that will cut off the top title bar of the Survey Wall. ::: #### Android EDM settings We recommend using the **Default settings, EXCEPT Explode AARs**. **Assets -> External Dependency Manager -> Android Resolver -> Settings** **Uncheck Explode AARs** (no longer recommended by Google) After importing EDM and **TapResearchSDK.unitypackage** a dialog might pop-up asking to ENABLE AUTO-RESOLVE dependencies. Be sure to click ENABLE. We recommend auto-resolve because it makes integration much easier. #### Android API levels Build Settings -> Player Settings -> Other Settings, set **Minimum API Level 24**. **Target API Level to HIGHEST INSTALLED (33 or higher recommended)**. #### Android ProGuard rules Copy and paste [our proguard rules](https://github.com/TapResearch/demo-apps/blob/master/android/FullFeatureSet/TapResearchKotlinFullDemo/app/proguard-rules.pro) to your existing proguard file located at: **Assets/Plugins/Android/proguard-user.txt** If you do not have this file, please create it with our proguard rules. #### Unity 2020 or earlier If you use Unity 2020 or earlier, please **uncheck** all **Minify** options including Debug, Release, and R8 under **Player Settings -> Publishing Settings** ### iOS project setup #### Frameworks The **TapResearchSDK includes a post-process script** that runs after compilation to add required frameworks and linker flags to the generated Xcode project for iOS. #### Build post process script The Unity package includes a `BuildPostprocessoriOS.cs` script that performs some necessary modifications to the Xcode project that is generated when exporting a build from the Unity editor. The script extracts and embeds `TapResearchSDK.xcframework` and configures the generated Xcode project. ## Integration Add `using TapResearch;` to any script that calls the SDK. ### Callbacks #### SDK ready callback example This function will be called when the SDK is ready to show content. It is a good time to update TapResearch with initial user attributes. See [User attributes and targeting](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes) for more. User attributes are used to target specific users with content (ex: an offer). The most recent values received are used when determining which content to show. See [Send user attributes](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#send-user-attributes) for supported values and update options. You can send user attributes as soon as the SDK is ready. This is done by calling `SendUserAttributes` with a `Dictionary` of attributes. We suggest sending as many attributes as you think that you'll want to target on for surveys, special awards, etc. ```csharp public void TapSdkReady() { Debug.Log("TapResearchSDK ready, going to send user attributes"); Dictionary userAttributes = new Dictionary(); userAttributes["some_string"] = "a string value"; userAttributes["some_number"] = "12"; userAttributes["another_number"] = 12; userAttributes["boolean"] = "true"; userAttributes.Add("another_string", "it's another string!"); DateTime now = DateTime.UtcNow; string iso8601String = now.ToString("o"); userAttributes["iso8601_date"] = iso8601String; TapResearchSDK.SendUserAttributes(userAttributes); } ``` #### Rewards callback info The SDK will check if the user has unredeemed rewards in the following events: * On SDK initialization * When the user exits TapResearch The `TapResearchRewardReceived` interface is the reward listener that will handle the new rewards that the player earned in a session. The reward information will be encapsulated in the `TRReward` object with the following methods: | Method name |Type| Description | |:----------------------|:----|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | TransactionIdentifier |String| The reward unique identifier | | CurrencyName |String| The virtual currency name | | PlacementIdentifier |String| The placement identifier that started the session identifier | | PlacementTag |String| The placement tag that started the session identifier | | RewardAmount |int| The reward amount in virtual currency. The value will automatically be converted to your virtual currency based on the exchange rate you specified in the [app settings](https://www.tapresearch.com/supplier_dashboard/overview). | | PayoutEvent |string| The action the user was rewarded for; see [payout event values](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward#payout-event-values). | **Setting the rewards callback** * You can set different reward callbacks in different scenes, when setting different callbacks for different scenes then the best practice is to set a scene's callback to null before loading a new scene. When setting a new reward callback the old one is replaced. * You can set one, and make sure that the object it is attached to is non-destructible so that it survives scene loads (see https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html). #### Rewards callback example This function will receive the rewards that the user earned in a session as an array of `TRReward` objects. You can set this before or after calling `Configure()`. ```csharp private void TapResearchRewardsReceived(TRReward[] rewards) { foreach (TRReward reward in rewards) { Debug.Log("Tap Rewards: You've earned " + reward.RewardAmount + " " + reward.CurrencyName + ". " + reward.TransactionIdentifier); } } ``` #### Content shown / dismissed callback setup :::info These are required but do not need to do anything ::: ```csharp public void TapContentShown(string placementTag) { Debug.Log("Survey Content Opened"); } public void TapContentDismissed(string placementTag) { Debug.Log("Survey Content Dismissed"); } ``` #### Error callback setup :::info This is required but does not need to do anything ::: Any error received from the TapResearch SDK will be passed back to this function ```csharp public void TapResearchDidError(TRError error) { Debug.Log("TapResearch Error: " + error.ErrorDescription); } ``` #### Quick Question response callback New to Quick Questions? See [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions) for what they are and the response payload. **Setting the Quick Question response callback** * You can set different Quick Question response callbacks in different scenes, when setting different callbacks for different scenes then the best practice is to set a scene's callback to null before loading a new scene. When setting a new Quick Question response callback the old one is replaced. * You can set one, and make sure that the object it is attached to is non-destructible so that it survives scene loads (see https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html). This function will receive a Quick Question response object. You can set this before or after calling `Configure()`. ```csharp private void TapQQResponseReceived(TRQQDataPayload payload) { Debug.Log("TapResearchExample: TRQQDataPayload received! placement: " + payload.PlacementTag + " userIdentifier: " + payload.UserIdentifier); } ``` ### Initialization Initialize the TapResearchSDK as early as possible once unique user identifier is known. Please note that the `Configure()` method only needs to be called once on app launch, make sure to call `DontDestroyOnLoad()` in the C# code for the class that implements the SDK's callbacks. :::note You must declare your callback functions prior to calling `Configure()`. ::: Your iOS and Android apps have different API tokens. Use pre-processor directives so Unity knows which API token to use when you build your app. :::tip The user identifier passed through initialization must be **unique**! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service. ::: ```csharp private static string tapAPIToken; private static string userIdentifier = "YOUR_UNIQUE_USER_ID"; private static string placementTag = "home-screen"; void Awake() { DontDestroyOnLoad(this); // Don't destroy this object on scene change #if UNITY_ANDROID tapAPIToken = "90d0d66d0213cf0245254e3d3b222e92"; // Public Test Android, replace with your own API token #elif UNITY_IPHONE tapAPIToken = "ed51703ae058f16bd196a81f9fc1bfcb"; // Public Test iOS, replace with your own API token #endif TapResearchSDK.TapContentShown = TapContentShown; TapResearchSDK.TapContentDismissed = TapContentDismissed; TapResearchSDK.TapResearchDidError = TapResearchDidError; TapResearchSDK.TapResearchSdkReady = TapSdkReady; TapResearchSDK.Configure(tapAPIToken, userIdentifier); // Set your reward handler when you are ready to receive the rewards TapResearchSDK.TapResearchRewardReceived = TapResearchRewardsReceived; // Set your quick question handler when you are ready to receive quick question result data TapResearchSDK.TapResearchQQResponseReceived = TapQQResponseReceived; } ``` ### Displaying a placement Wrapping in `CanShowContent` is optional but recommended. It will check if the placement is ready to be shown before attempting to show it. If the placement is not ready, it will return false and you can try again later. ```csharp public void showSurveyContent() { if (TapResearchSDK.CanShowContent(placementTag)) //DefaultTest Placement { TapResearchSDK.ShowContentForPlacement(placementTag); //DefaultTest Placement } } ``` ### Passing custom parameters :::note Parameters can only be ASCII characters, Unicode is not supported. ::: ```csharp ShowContentForPlacement(string placementTag, Dictionary customParameters); ``` ```csharp public void showSurveyContentWithParameters() { if (TapResearchSDK.CanShowContent(placementTag)) //CustomParam Placement { Dictionary customParameters = new Dictionary(); //Parameters customParameters["player_attribute"] = "my-vip"; customParameters["data_value"] = "integer"; customParameters["another_number"] = "12"; customParameters.Add("another_string", "it's another string!"); TapResearchSDK.ShowContentForPlacement(placementTag, customParameters); } } ``` ### Send user attributes :::note User Attributes can only be ASCII characters, Unicode is not supported. ::: ```csharp SendUserAttributes(Dictionary userAttributes, bool clearPreviousAttributes); ``` Attribute names must be strings and must not begin with `tapresearch_`, which is reserved for TapResearch. Use string or numeric values for targeting. For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. Set `clearPreviousAttributes` to `false` to merge new values with existing attributes, or `true` to replace the attributes you previously sent. ```csharp Dictionary userAttributes = new Dictionary(); userAttributes["some_string"] = "a string value"; userAttributes["some_number"] = "12"; userAttributes["another_number"] = 12; userAttributes["boolean"] = "true"; userAttributes.Add("another_string", "it's another string!"); TapResearchSDK.SendUserAttributes(userAttributes, true); ``` #### Send user attributes with `Configure` You can send a User Attributes dictionary when initializing the SDK. ```csharp Dictionary userAttributes = new Dictionary(); userAttributes["some_string"] = "a string value"; userAttributes["some_number"] = "12"; userAttributes["another_number"] = 12; userAttributes["boolean"] = "true"; userAttributes.Add("another_string", "it's another string!"); TapResearchSDK.ConfigureWithUserAttributes(tapAPIToken, userIdentifier, userAttributes, true); ``` Configure with user attributes takes the following parameters: an API token string, player identifier string, user attributes dictionary and a boolean indicating whether the user attributes replace existing attributes or are an update to previously-set attributes. :::info If user attributes are known at SDK initialization, it is preferable to use `ConfigureWithUserAttributes` compared to `SendUserAttributes`. This will result in quicker load times for targeted content. ::: ### Setting a new player/user identifier A new user identifier can be set by passing a user identifier string to `UpdateCurrentUser()`: ```csharp TapResearchSDK.UpdateCurrentUser(new_user_identifier); ``` ### Checking if the SDK is ready The SDK lets the app know it is ready using a callback. Using this callback is described in the [SDK Ready callback example](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#sdk-ready-callback-example) above. You can also check if the SDK is ready using the `IsReady()` call: ```csharp if (TapResearchSDK.IsReady()) { ... } ``` ### Getting placement details Get placement details by calling: ```csharp TRPlacementDetails TapResearchSDK.GetPlacementDetails(string placementTag) ``` `TRPlacementDetails` is defined as: ```csharp public struct TRPlacementDetails { public readonly string Name; public readonly string ContentType; public readonly string CurrencyName; public readonly bool IsSale; public readonly string SaleType; public readonly string SaleEndDate; public readonly double SaleMultiplier; public readonly string SaleDisplayName; public readonly string SaleTag; public readonly TRBonusBarProgress BonusBarProgress; } public struct TRBonusBarProgress { public readonly bool IsActive; public readonly int CurrentCompletes; public readonly string BonusWindowEndAt; public readonly TRBonusTier[] BonusTiers; public readonly Nullable Error; } public struct TRBonusTier { public readonly int TierNumber; public readonly int CompletesNeeded; public readonly int RewardAmount; public readonly string Status; } ``` ### Granting boosts Call `GrantBoost` with a configured [User Boosts](https://supply-docs.tapresearch.com/docs/3.x/features/user-boosts) tag. The result is delivered to the `TapResearchGrantBoostResponse` delegate: ```csharp // Set the handler before granting, for example alongside your other delegates TapResearchSDK.TapResearchGrantBoostResponse = TapGrantBoostResponse; TapResearchSDK.GrantBoost("YOUR_BOOST_TAG"); void TapGrantBoostResponse(TRGrantBoostResponse response) { if (response.Success) { // Boost granted } else { Debug.Log("Grant boost failed for " + response.BoostTag + ": " + response.Error.ErrorDescription); } } ``` ## Screen orientation for landscape applications TapResearch works better in portrait mode. Using Unity Player Settings to disable Portrait orientation may result in problems rendering some surveys. ### TapResearch surveys and Quick Questions To make sure that TapResearch surveys display in portrait mode when you have locked your game in landscape mode set the `Screen.orientation` to `ScreenOrientation.Portrait`. Use co-routines or scene-loads to make sure it is in effect before opening any TapResearch content. We recommend switching into portrait orientation if you have locked your game to landscape. ### TapResearch banners & interstitials Banners and interstitials can work in any orientation, however when tapped they will go on to show surveys. For placements starting with a banner or interstitial we recommend switching to `Screen.orientation` to `ScreenOrientation.AutoRotation` with landscape left, right and portrait enabled for auto-rotation: ```csharp Screen.autorotateToPortrait = true; Screen.autorotateToLandscapeLeft = true; Screen.autorotateToLandscapeRight = true; ``` ## Unity example projects Example projects for Unity can be found in [GitHub](https://github.com/TapResearch/demo-apps). The examples show how to integrate the TapResearchSDK package and how orientation can be managed. ### Full feature-set Unity example In the full feature-set example you can set your API tokens in two ways: setting variable in C# ([See source](https://github.com/TapResearch/demo-apps/blob/master/unity/FullUnityExample/Assets/TapResearchSDKExample/TapResearchExample.cs)), or using the Unity editor. If you would like to set your API token using the editor: 1. Open the example; 2. Open the `TapResearchTestScene` scene from the `TapResearchSDKExample` folder in the assets list; 3. Select `Canvas` in the hierarchy panel; 4. In the Inspector panel for `Canvas` scroll to the OS-specific token values and enter your API tokens. If you prefer to set the API token value in C#: 1. Open `TapResearchExample.cs` in your favorite editor 2. Set your API tokens in the Awake() function before `TapResearchSDK.Configure(tapAPIToken, tapPlayerUserId);` on [Line 57](https://github.com/TapResearch/demo-apps/blob/fbe74eb989e652dc7b90e0dd65b0c2426e5d58b9/unity/FullUnityExample/Assets/TapResearchSDKExample/TapResearchExample.cs#L57). ### Wall Unity example This example contains a single simple scene and C# source file in `Assets/TapResearchWallDemo` that shows how to present TapResearch content and respond to show, dismiss and rewards callbacks. ### Native Survey Tiles Unity example This example shows how to integrate the Native Survey Tiles feature. It contains 2 scenes: 1. `TapResearchTestScene`: Starts the TapResearchSDK, shows how to check if a Native Survey Tiles placement has surveys; 2. `WallPreviewScene`: Shows how to use the `TRSurvey` objects returned by getting surveys using `TapResearchSDK.GetSurveysForPlacement()`. ### Unity example project orientation handling We have included a simple orientation changer class [`TROrientationChanger.cs`](https://github.com/TapResearch/demo-apps/blob/master/unity/FullUnityExample/Assets/TapResearchSDKExample/TROrientationChanger.cs) in the full-feature-set example project that illustrates how to change orientation. When our example starts we set (lock) to landscape orientation using ```csharp Screen.orientation = ScreenOrientation.LandscapeLeft; ``` When we are ready to show TapResearch content we use the included example orientation changer to change to portrait orientation: ```csharp orientationChanger.SetPortrait(OnOrientationChangedToPortrait); ``` In the completion handler for SetPortrait we show the placement: ```csharp private void OnOrientationChangedToPortrait() { TapResearchSDK.ShowContentForPlacement(placementTag); } ``` When the user dismisses the surveys we do the reverse to return to landscape orientation: ```csharp orientationChanger.SetLandscapeLeft(OnOrientationChangedToLandscapeLeft); ``` ## Need help? Email [developers@tapresearch.com](mailto:developers@tapresearch.com). **Stay updated:** watch the [TapResearch Unity SDK on GitHub](https://github.com/TapResearch/TapResearch-Unity-SDK) to get notified about new releases. --- # Unity Native Survey Tiles Source: https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/unity-surveys :::info Test mode If your app is in test mode, you **must** use a test user. Add test users in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under **More → Test devices**. ::: ## Installation Start with the [Unity integration guide](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity) if you haven't set up the SDK yet. Example apps are described in [Unity example projects](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#unity-example-projects), including a Native Survey Tiles example. ### Before you begin * You must be on SDK v3.4.1+ to have access to Native Survey Tiles. * The wall preview feature needs to be enabled and set up by the TapResearch team so please reach out to your account manager when you are ready to get started * Native Survey Tiles should be on a dedicated placement, separate from the standard Survey Wall (if applicable) * **Always wait** until the SDK is ready (via the `TapResearchSdkReady` callback or by checking `TapResearchSDK.IsReady()`) before calling any Native Survey Tiles interfaces :::info Support Any other questions? Reach out to your account manager or via Slack and we'll be happy to help! ::: ## Introduction **Native Survey Tiles** (formerly *Survey Wall Preview*) let you provide users with available surveys, directly inside your app. Some benefits of this include: * **Custom UI** — Create your own UI presentation with survey tiles/cards * **Direct survey access** — Provide users with direct entry into surveys they choose --- ### Key considerations & UI design When designing your survey tiles/cards, consider these elements: ### 1. The profile survey New users must complete the introductory profile survey before they can access other surveys. For these new users, the SDK will return **only** this survey until they have completed it. * Only shown to users that are new to TapResearch * Will always be returned by itself, with a survey identifier of `profile` * Display example: ### 2. Survey details After completing the profile survey, users should then start receiving real surveys. Surveys will have the following attributes: ```csharp public class TRSurvey { // Primary attributes [SerializeField] private string surveyIdentifier; // Unique identifier, will be used when a user taps on a survey [SerializeField] private string currencyName; // The name of the reward currency [SerializeField] private int lengthInMinutes; // The estimated time to complete the survey [SerializeField] private double rewardAmount; // The reward amount for taking the survey [SerializeField] private bool isHotTile; // Whether the survey should be designated as a "hot tile" [SerializeField] private string category; // The survey's category, if it has one // Sale attributes [SerializeField] private bool isSale; // Whether a currency sale is currently active [SerializeField] private double saleMultiplier; // The multiplier being applied to the sale [SerializeField] private double preSaleRewardAmount; // The pre-multiplier reward amount, for enhancing UI during a sale [SerializeField] private string saleEndDate; // If a sale is active, the UTC date it will end public string SurveyIdentifier { get { return surveyIdentifier; } } public string CurrencyName { get { return currencyName; } } public int LengthInMinutes { get { return lengthInMinutes; } } public double RewardAmount { get { return rewardAmount; } } public bool IsHotTile { get { return isHotTile; } } public string Category { get { return category; } } public bool IsSale { get { return isSale; } } public double SaleMultiplier { get { return saleMultiplier; } } public double PreSaleRewardAmount { get { return preSaleRewardAmount; } } public string SaleEndDate { get { return saleEndDate; } } } ``` * We recommend displaying all primary data points to allow users to choose which survey length/reward they want * Display example: ### 3. "Hot" tiles Hot tiles are surveys with exceptional scores, typically offering the best time-to-reward ratio. Displaying a special UI highlight for these surveys is recommended, as they tend to increase user engagement and click-through rates. * Display example: ### 4. Currency Sales When running promotional currency sales, the relevant sale attributes of the survey object will populate, allowing you to show enhanced UIs and CTAs to users. We recommend using this data to display the extra rewards boost on the individual tiles. * Display example: --- ## Integration ### SDK interfaces #### Checking for available surveys When you have received notification that the SDK is ready (see [Checking if the SDK is ready](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#checking-if-the-sdk-is-ready)) you should first check if there are surveys available for your placement: ```csharp bool flag = TapResearchSDK.HasSurveys("placementTag"); ``` #### Getting available surveys Get the surveys for your placement. ```csharp TRSurvey[] surveys = TapResearchSDK.GetSurveysForPlacement("placementTag"); ``` #### Showing a survey Showing a survey works similarly to `ShowContentForPlacement` used for showing TapResearch survey content: after checking that surveys are available, call `ShowSurveyForPlacement` with the survey's identifier and the placement tag. Content shown and dismissed events are delivered to the `TapContentShown` and `TapContentDismissed` callbacks; see [Callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#callbacks) for details. ```csharp TapResearchSDK.ShowSurveyForPlacement(survey.SurveyIdentifier, placementTag); ``` If you need to pass custom parameters you can use `ShowSurveyForPlacement` and pass a dictionary: ```csharp Dictionary customParameters = new Dictionary(); customParameters["player_attribute"] = "my-vip"; customParameters["data_value"] = "integer"; customParameters["another_number"] = 12; customParameters.Add("another_string", "it's another string!"); TapResearchSDK.ShowSurveyForPlacement(survey.SurveyIdentifier, placementTag, customParameters); ``` ### Callback Native Survey Tiles adds a **required** callback that lets you know when surveys have been refreshed. #### Setting the callback Assigning a handler to `TapResearchSDK.TapResearchSurveysRefreshed` turns the callback on; set it to `null` to turn it off, for example when leaving the scene that shows surveys. #### Handling the callback You will see calls to the callback function when surveys have been completed, after any TapResearch content has been dismissed. **Required:** Make sure to set the SDK's `TapResearchSDK.TapResearchSurveysRefreshed` callback: The callback is defined as `void TapResearchSurveysRefreshDelegate(string placementTag)`. To set the callback: ```csharp TapResearchSDK.TapResearchSurveysRefreshed = TapResearchSurveysRefreshed; ``` Implement the callback: ```csharp private void TapResearchSurveysRefreshed(string placementTag) { TRSurvey[] surveys = TapResearchSDK.GetSurveysForPlacement(placementTag); // Update displayed survey buttons } ``` --- # Flutter Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter :::tip What you'll do 1. [Install the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#installation) 2. [Set up callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#callbacks) 3. [Initialize the SDK](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#initialization) 4. [Display a placement](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#displaying-a-placement) Later sections cover rewards, user attributes, the user identifier, Quick Questions, boosts, and placement details. ::: ## Installation The TapResearch Flutter plugin with an example app is available on GitHub: https://github.com/TapResearch/tapresearch_flutter_plugin/ ### Requirements * Flutter 3.41.7 or later (Dart 3.11.5 or later) * Android `minSdk` 24 * iOS 13.0 (iOS 15.0 from 3.8.1) ### Add the plugin Replace `` below with your SDK version from [pub.dev](https://pub.dev/packages/tapresearch_flutter_plugin/versions). Add the `tapresearch_flutter_plugin` to your `pubspec.yaml` file: ```yaml dependencies: tapresearch_flutter_plugin: ``` Or run the following command: ```bash flutter pub add tapresearch_flutter_plugin: ``` ## Integration :::info Test mode If your app is in test mode, you **must** use a test user. Configure test users in **"Test Devices"** on the dashboard. ::: ### Callbacks The SDK uses callbacks to notify your app of certain events. You should implement these interfaces and provide them to the SDK. #### SDK ready callback This function will be called when the SDK is ready to show content. ```dart class MySdkReadyCallback implements TRSdkReadyCallback { @override void onTapResearchSdkReady() { print("TapResearch SDK is ready"); } } ``` #### Rewards callback The SDK will notify you when a user has earned rewards. ```dart class MyRewardCallback implements TRRewardCallback { @override void onTapResearchDidReceiveRewards(List rewards) { for (var reward in rewards) { print("Received reward: ${reward.rewardAmount} ${reward.currencyName}"); } } } ``` `TRReward` contains the following properties (see [TRReward](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward) for a description of each field): | Property | Type | | :--- | :--- | | `transactionIdentifier` | `String?` | | `placementIdentifier` | `String?` | | `placementTag` | `String?` | | `currencyName` | `String?` | | `rewardAmount` | `double?` | | `payoutEventType` | `PayoutTypes?` — `profileReward` (0), `partialPayout` (1), `fullPayout` (3, survey complete), `quickQuestionsPayout` (9) | #### Error callback Handle any errors that may occur. Errors are passed as a `TRError` with a `code` and a `description`. ```dart class MyErrorCallback implements TRErrorCallback { @override void onTapResearchDidError(TRError error) { print("TapResearch Error: ${error.description} (Code: ${error.code})"); } } final onError = MyErrorCallback(); ``` #### Content callbacks Notify your app when content is shown or dismissed. Pass a content callback to each `showContentForPlacement` or `showSurveyForPlacement` call. ```dart class MyContentCallback implements TRContentCallback { @override void onTapResearchContentShown(String placementTag) { print("Content shown for $placementTag"); } @override void onTapResearchContentDismissed(String placementTag) { print("Content dismissed for $placementTag"); } } ``` ### Initialization Initialize the TapResearch SDK as early as possible. It is required to use different API tokens for iOS and Android. :::tip The user identifier passed through initialization must be **unique** per user! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service. ::: ```dart import 'dart:io'; import 'package:tapresearch_flutter_plugin/tapresearch_flutter_plugin.dart'; final _plugin = TapresearchFlutterPlugin(); void initTapResearch() { final String apiToken = Platform.isAndroid ? "YOUR_ANDROID_API_TOKEN" : "YOUR_IOS_API_TOKEN"; final String userIdentifier = "YOUR_UNIQUE_USER_ID"; _plugin.initialize( apiToken: apiToken, userIdentifier: userIdentifier, sdkReadyCallback: MySdkReadyCallback(), errorCallback: onError, rewardCallback: MyRewardCallback(), ); } ``` :::info If user attributes are known at SDK initialization, it is preferable to pass them through `userAttributes` compared to using `sendUserAttributes`. This will result in quicker load times for targeted content. ::: ```dart _plugin.initialize( apiToken: apiToken, userIdentifier: userIdentifier, sdkReadyCallback: MySdkReadyCallback(), errorCallback: onError, rewardCallback: MyRewardCallback(), userAttributes: { "age": 25, "is_vip": 1, }, clearPreviousAttributes: false, ); ``` ### Checking if the SDK is ready You can check if the SDK is ready using the `isReady()` method: ```dart void checkSdkReady() async { bool isReady = await _plugin.isReady(); print("SDK Ready: $isReady"); } ``` ### Displaying a placement You can check whether a placement is available by using `canShowContentForPlacement()`. It is recommended that you wrap `showContentForPlacement` calls with this check. ```dart void showPlacement(String placementTag) async { bool canShow = await _plugin.canShowContentForPlacement(placementTag, onError); if (canShow) { _plugin.showContentForPlacement( tag: placementTag, contentCallback: MyContentCallback(), errorCallback: onError, ); } } ``` ### Passing custom parameters Custom parameters allow you to receive additional information about the user when they complete a survey. ```dart void showPlacementWithCustomParameters(String placementTag) { Map customParameters = { "custom_param_1": "value1", "is_vip": true, }; _plugin.showContentForPlacement( tag: placementTag, customParameters: customParameters, contentCallback: MyContentCallback(), errorCallback: onError, ); } ``` ### Sending user attributes User attributes are used to target content to specific users. Attribute names must be strings and must not begin with `tapresearch_`, which is reserved for TapResearch. Use string or numeric values for targeting. For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. Set `clearPreviousAttributes` to `false` to merge new values with existing attributes, or `true` to replace the attributes you previously sent. ```dart void sendUserAttributes() { Map userAttributes = { "age": 25, "gender": "female", "is_vip": 1, }; _plugin.sendUserAttributes( userAttributes: userAttributes, clearPreviousAttributes: false, errorCallback: onError, ); } ``` ### Updating user identifier You can set the user identifier at any time. ```dart void updateUserIdentifier(String newUserId) { _plugin.setUserIdentifier(newUserId); } ``` ### Quick Questions New to Quick Questions? See [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions) for what they are, and [Handling Quick Question Responses](https://supply-docs.tapresearch.com/docs/3.x/3.2-only/handling-quick-question-responses) for other ways to receive the answers. To receive Quick Question responses in your app, implement `TRQQDataCallback` and pass it to `initialize` as `qqDataCallback`. It is called whenever a user completes a Quick Question. ```dart class MyQQDataCallback implements TRQQDataCallback { @override void onQuickQuestionDataReceived(QQPayload data) { print("Quick Question ${data.surveyIdentifier} answered on ${data.placementTag}"); for (final question in data.questions) { print("${question.questionText}: ${question.userAnswer?.value}"); } } } _plugin.initialize( apiToken: apiToken, userIdentifier: userIdentifier, sdkReadyCallback: MySdkReadyCallback(), errorCallback: onError, qqDataCallback: MyQQDataCallback(), ); ``` #### QQPayload class | Property | Type | Description | | :--- | :--- | :--- | | `surveyIdentifier` | `String` | The Quick Question identifier. | | `appName` | `String` | Your app's name. | | `apiToken` | `String` | The app's API token. | | `sdkVersion` | `String` | The SDK version. | | `platform` | `String` | The platform. | | `placementTag` | `String` | The placement the Quick Question was shown on. | | `userIdentifier` | `String` | The user identifier. | | `userLocale` | `String` | The user's locale. | | `seenAt` | `String` | When the Quick Question was shown. | | `complete` | `Complete?` | `completeIdentifier` and `completedAt`. | | `questions` | `List` | Each `Question` has `questionIdentifier`, `questionText`, `questionType` and `userAnswer` (`UserAnswer?` with `value` and `identifiers`). | | `targetAudience` | `List>?` | The targeting filters, each with `filter_attribute_name`, `filter_operator`, `filter_value` and `user_value` keys. | ### Grant boost Boosts are temporary reward multipliers that can be granted to users. ```dart class MyGrantBoostListener implements TRGrantBoostResponseListener { @override void onGrantBoostResponse(TRGrantBoostResponse response) { if (response.success == true) { print("Boost granted successfully: ${response.boostTag}"); } else { print("Failed to grant boost: ${response.error?.description}"); } } } void applyBoost(String boostTag) { _plugin.grantBoost( boostTag, listener: MyGrantBoostListener(), ); } ``` ### Getting placement details :::warning Don't cache placement details Placement details can change as our SDK refreshes placements in the background, so it is not recommended to store or cache these values. Instead, refetch the details each time you need to use them, e.g. to display something in your UI. ::: Get placement details by calling `getPlacementDetails(placementTag, errorListener: ...)`. ```dart void getPlacementInfo(String placementTag) async { TRPlacementDetails? details = await _plugin.getPlacementDetails( placementTag, errorListener: onError, ); if (details != null) { print("Placement Name: ${details.name}"); print("Is Sale Active: ${details.isSale}"); } } ``` #### TRPlacementDetails class `TRPlacementDetails` contains the following properties: | Property | Type | Description | | :--- | :--- | :--- | | `name` | `String?` | The name of the placement. | | `contentType` | `String?` | The type of content associated with the placement. | | `currencyName` | `String?` | The name of the currency rewarded by this placement. | | `isSale` | `bool?` | Indicates if there is an active sale. | | `saleType` | `String?` | The category or type of the active sale. | | `saleEndDate` | `String?` | When the sale expires (UTC date string). | | `saleMultiplier` | `double?` | The reward multiplier during a sale. | | `saleDisplayName` | `String?` | The display name for the sale. | | `saleTag` | `String?` | A tag associated with the sale. | | `bonusBarProgress` | `TRBonusBarProgress?` | The user's progress toward a bonus. | #### TRBonusBarProgress class | Property | Type | Description | | :--- | :--- | :--- | | `isActive` | `bool?` | Indicates if the bonus bar is currently active. | | `currentCompletes` | `int?` | The number of survey completions achieved toward the bonus. | | `bonusWindowEndAt` | `String?` | When the bonus window expires (UTC date string). | | `bonusTiers` | `List?` | A list of tiers available in the progress bar. | | `error` | `TRError?` | Error information if the progress could not be retrieved. | #### TRBonusTier class | Property | Type | Description | | :--- | :--- | :--- | | `tierNumber` | `int?` | The sequence number of the tier. | | `completesNeeded` | `int?` | Number of completions required to reach this tier. | | `rewardAmount` | `double?` | The reward granted upon completing this tier. | | `status` | `String?` | The current status of the tier. | ## Need help? Email [developers@tapresearch.com](mailto:developers@tapresearch.com). **Stay updated:** see [tapresearch_flutter_plugin on pub.dev](https://pub.dev/packages/tapresearch_flutter_plugin) for new releases. --- # Flutter Native Survey Tiles Source: https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/flutter-surveys :::info Test mode If your app is in test mode, you **must** use a test user. Add test users in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under **More → Test devices**. ::: ## Installation * **Integration guide:** See the [Flutter SDK documentation](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter) * **Sample Survey Wall Preview app with custom tiles:** Available on [GitHub](https://github.com/TapResearch/tapresearch_flutter_plugin) ### Before you begin * The wall preview feature needs to be enabled and set up by the TapResearch team so please reach out to your account manager when you are ready to get started * Native Survey Tiles should be on a dedicated placement, separate from the standard Survey Wall (if applicable) * **Always wait** until the SDK is ready (via the `onTapResearchSdkReady` callback or by checking `isReady()`) before calling any Native Survey Tiles interfaces :::info Support Any other questions? Reach out to your account manager or via Slack and we'll be happy to help! ::: ## Introduction **Native Survey Tiles** (formerly *Survey Wall Preview*) let you provide users with available surveys, directly inside your app. Some benefits of this include: * **Custom UI** — Create your own UI presentation with survey tiles/cards * **Direct survey access** — Provide users with direct entry into surveys they choose --- ### Key considerations & UI design When designing your survey tiles/cards, consider these elements: ### 1. The profile survey New users must complete the introductory profile survey before they can access other surveys. For these new users, the SDK will return **only** this survey until they have completed it. * Only shown to users that are new to TapResearch * Will always be returned by itself, with a survey identifier of `profile` * Display example: ### 2. Survey details After completing the profile survey, users should then start receiving real surveys. Surveys will have the following attributes: ```dart class TRSurvey { // Primary attributes final String? surveyId; // Unique identifier, will be used when a user taps on a survey final int? lengthInMinutes; // The estimated time to complete the survey final double? rewardAmount; // The reward amount for taking the survey final String? currencyName; // The name of the reward currency final bool? isHotTile; // Whether the survey should be designated as a "hot tile" final String? category; // The survey's category, if it has one // Sale attributes final bool? isSale; // Whether a currency sale is currently active final String? saleEndDate; // If a sale is active, the UTC date it will end final double? saleMultiplier; // The multiplier being applied to the sale final double? preSaleRewardAmount; // The pre-multiplier reward amount, for enhancing UI during a sale } ``` * We recommend displaying all primary data points to allow users to choose which survey length/reward they want * Display example: ### 3. "Hot" tiles Hot tiles are surveys with exceptional scores, typically offering the best time-to-reward ratio. Displaying a special UI highlight for these surveys is recommended, as they tend to increase user engagement and click-through rates. * Display example: ### 4. Currency Sales When running promotional currency sales, the relevant sale attributes of the survey object will populate, allowing you to show enhanced UIs and CTAs to users. We recommend using this data to display the extra rewards boost on the individual tiles. * Display example: --- ## Integration The code below demonstrates how to: - Fetch surveys after SDK initialization - Set up a surveys refreshed listener (**required**) **Key points:** * Fetch surveys **only after** the SDK Ready event * Render the `TRSurvey` objects in your UI (see `SurveyWallPreviewScreen` in the [Sample App](https://github.com/TapResearch/tapresearch_flutter_plugin/blob/master/example/lib/main.dart)) * **Required:** Set the surveys refreshed listener to get updates when surveys change :::warning Attempting to fetch surveys **before** SDK Ready will result in an error and no surveys will be returned. ::: ### Initialization Initialize the TapResearch SDK as early as possible. It is required to use different API tokens for iOS and Android. :::tip The user identifier passed through initialization must be **unique** per user! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service. ::: The callback classes and `onError` (a `TRErrorCallback`) are defined in [Callbacks](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#callbacks). ```dart import 'dart:io'; import 'package:tapresearch_flutter_plugin/tapresearch_flutter_plugin.dart'; final _plugin = TapresearchFlutterPlugin(); void initTapResearch() { final String apiToken = Platform.isAndroid ? "YOUR_ANDROID_API_TOKEN" : "YOUR_IOS_API_TOKEN"; final String userIdentifier = "YOUR_UNIQUE_USER_ID"; _plugin.initialize( apiToken: apiToken, userIdentifier: userIdentifier, sdkReadyCallback: MySdkReadyCallback(), errorCallback: onError, rewardCallback: MyRewardCallback(), ); } ``` ### Set the surveys refreshed listener Implement `TRSurveysRefreshedListener` to be notified when surveys are refreshed and available for a placement. When surveys are refreshed, we recommend calling `getSurveysForPlacement()` so you can re-render your custom UI, as survey tile values may have changed. It could also be a good time to call `getPlacementDetails()` because the `placementDetails.contentType` may have changed. Possible values could be `profiler` or `survey_wall_preview` or another string value. ```dart class MySurveysRefreshedListener implements TRSurveysRefreshedListener { @override void onSurveysRefreshedForPlacement(String placementTag) { print("Surveys refreshed for $placementTag"); /* This is a good time to call getSurveysForPlacement() to re-render your SWP screen and / or getPlacementDetails() where the returned placementDetails.contentType can be 'profiler' or 'survey_wall_preview' or another string value */ } } ``` Provide the listener to the SDK: ```dart _plugin.setSurveysRefreshedListener(MySurveysRefreshedListener()); ``` ### Has surveys for placement Use `hasSurveysForPlacement()` to check whether a placement has surveys available. ```dart void checkForSurveys(String placementTag) async { bool hasSurveys = await _plugin.hasSurveysForPlacement(placementTag, onError); print("Has surveys: $hasSurveys"); } ``` ### Get surveys for placement Use `getSurveysForPlacement()` to fetch the list of available surveys for a placement tag. ```dart void fetchSurveys(String placementTag) async { List? surveys = await _plugin.getSurveysForPlacement(placementTag, onError); if (surveys != null && surveys.isNotEmpty) { for (var survey in surveys) { print("Available Survey: ${survey.surveyId} - Reward: ${survey.rewardAmount}"); } } } ``` ### Show survey When a user selects a survey from your custom UI, use `showSurveyForPlacement()` to open it. You can also pass `customParameters`, as with `showContentForPlacement()`. ```dart void openSurvey(String placementTag, String surveyId) { _plugin.showSurveyForPlacement( placementTag: placementTag, surveyId: surveyId, contentCallback: MyContentCallback(), errorCallback: onError, ); } ``` ### TRSurvey class | Property | Type | Description | | :--- | :--- | :--- | | `surveyId` | `String?` | Unique identifier for the survey; pass it to `showSurveyForPlacement()`. | | `lengthInMinutes` | `int?` | Estimated time to complete in minutes. | | `rewardAmount` | `double?` | The reward amount for completion. | | `currencyName` | `String?` | The name of the currency. | | `isSale` | `bool?` | Indicates if a sale is active for this survey. | | `saleEndDate` | `String?` | If a sale is active, the UTC date it ends. | | `saleMultiplier` | `double?` | The multiplier applied during the sale. | | `preSaleRewardAmount` | `double?` | The reward amount before the sale multiplier. | | `isHotTile` | `bool?` | Indicates if this is a high-priority survey. | | `category` | `String?` | The survey's category. | ### Getting placement details :::warning Don't cache placement details Placement details can change as our SDK refreshes placements in the background, so it is not recommended to store or cache these values. Instead, refetch the details each time you need to use them, e.g. to display something in your UI. ::: Get placement details by calling `getPlacementDetails(placementTag, errorListener: ...)`. ```dart void getPlacementInfo(String placementTag) async { TRPlacementDetails? details = await _plugin.getPlacementDetails( placementTag, errorListener: onError, ); if (details != null) { print("Placement Name: ${details.name}"); print("Is Sale Active: ${details.isSale}"); } } ``` See [Set the Surveys Refreshed Listener](https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/flutter-surveys#set-the-surveys-refreshed-listener) Example Survey Wall Preview screen with custom tiles can be seen at (SurveyWallPreviewScreen class): https://github.com/TapResearch/tapresearch_flutter_plugin/blob/master/example/lib/main.dart #### TRPlacementDetails class `TRPlacementDetails` contains the following properties: | Property | Type | Description | | :--- | :--- | :--- | | `name` | `String?` | The name of the placement. | | `contentType` | `String?` | The type of content associated with the placement. | | `currencyName` | `String?` | The name of the currency rewarded by this placement. | | `isSale` | `bool?` | Indicates if there is an active sale. | | `saleType` | `String?` | The category or type of the active sale. | | `saleEndDate` | `String?` | When the sale expires (UTC date string). | | `saleMultiplier` | `double?` | The reward multiplier during a sale. | | `saleDisplayName` | `String?` | The display name for the sale. | | `saleTag` | `String?` | A tag associated with the sale. | | `bonusBarProgress` | `TRBonusBarProgress?` | The user's progress toward a bonus. | #### TRBonusBarProgress class | Property | Type | Description | | :--- | :--- | :--- | | `isActive` | `bool?` | Indicates if the bonus bar is currently active. | | `currentCompletes` | `int?` | The number of survey completions achieved toward the bonus. | | `bonusWindowEndAt` | `String?` | When the bonus window expires (UTC date string). | | `bonusTiers` | `List?` | A list of tiers available in the progress bar. | | `error` | `TRError?` | Error information if the progress could not be retrieved. | #### TRBonusTier class | Property | Type | Description | | :--- | :--- | :--- | | `tierNumber` | `int?` | The sequence number of the tier. | | `completesNeeded` | `int?` | Number of completions required to reach this tier. | | `rewardAmount` | `double?` | The reward granted upon completing this tier. | | `status` | `String?` | The current status of the tier. | --- --- # Quick Question how-to Source: https://supply-docs.tapresearch.com/docs/3.x/3.2-only/quick-question-how-to Quick Questions allow you to ask up to **three questions** to your users at any point in your app. Currently available question types include: * Multiple choice (single-select or multi-select) * Open-ended text responses Follow the steps below to set up your first Quick Question: ## Step 1: Create a currency If you haven't already, [create a currency](https://supply-docs.tapresearch.com/docs/3.x/setup#add-a-currency) for rewarding users. ## Step 2: Create a placement Ensure you have a placement created for where the Quick Question will appear. A placement defines the physical location in your app (e.g., home screen, earn center). **Important:** Quick Questions must be attached to a placement whose **Placement type** is **"Pop-up"**, not "Survey wall". Unlike survey walls, Quick Questions are triggered automatically to users matching your targeting criteria—they don't require a button click. ## Step 3: Create your Quick Question content Once your placement is ready: 1. Click the **Create content** button in the upper right corner 2. Select **"Quick Questions"** ## Step 4: Build your questions In the content builder: 1. Choose your question type (multiple choice single-select, multiple choice multi-select, or open text) 2. Fill in the required fields 3. Add up to **3 questions** per Quick Question content ### Question guidelines **Multiple Choice Questions:** * No character limit on questions or answers, but check the preview to ensure readability * No limit on answer options, but consider user experience (more options = more scrolling) * **Best practice:** Keep answer options to 7 or fewer for optimal data quality * Use the randomize toggle to avoid order bias and maintain data integrity **Open-Ended Questions:** * Answers are limited to **250 characters** ## Step 5: Configure display settings Once your questions are composed: 1. Navigate to the **"Configure"** tab 2. Choose the platform(s) (iOS, Android, etc.) 3. Select the placement where the Quick Question will be shown ## Step 6: Set up rewards Determine whether to reward users for answering the Quick Question, and if so, specify the reward amount. ## Step 7: Add Survey Wall link (optional) Choose whether to include a link to the TapResearch survey wall at the end of the Quick Question. This allows users to continue to more survey opportunities. **Note:** * You can select any currency that has an associated survey wall in your app * The currency can differ from the one used for Quick Question rewards ## Step 8: Configure response settings Set your response limits: * Cap the number of participants * Set an end date/time for the Quick Question ## Step 9: Define target audience Specify which users will see this Quick Question based on their attributes. **Learn more:** For detailed information about user attributes and targeting, see our [User Attributes and Targeting documentation](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes). ## Step 10: Launch your Quick Question Once all steps are complete: 1. Click **"Launch Quick Question"** in the upper right corner 2. Your Quick Question will go live and responses will start coming in! --- **Need help?** Contact developers@tapresearch.com or your Account Manager for additional support. --- # Handling Quick Question responses Source: https://supply-docs.tapresearch.com/docs/3.x/3.2-only/handling-quick-question-responses ## Overview There are several ways to ingest Quick Question responses: * **In-app callback** - Real-time responses within your app * **Server-to-server postback** - Automated server notifications * **CSV download** - Manual export from the dashboard * **API download** - Programmatic data retrieval Select an approach based on how you plan to use the Quick Question response data. ## In-app callback In-app callbacks are fired whenever a user completes a Quick Question. **Use this method when you need real-time access to response data.** The Quick Question callback is registered differently on each platform. See the platform-specific documentation below for implementation details: * [iOS Quick Question response](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#quick-question-response) * [Android Quick Question response](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#quick-question-callback-setup-additional-information) * [Unity Quick Question response](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#quick-question-response-callback) * [React Native Quick Question response](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#quick-question-response) * [Flutter Quick Question response](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#quick-questions) ## Server-to-server postback Server-to-server postbacks are sent after a user answers a Quick Question, if a postback URL is configured in your app settings. **Note:** Setting up a postback URL does not prevent in-app callbacks from firing—both can be used simultaneously. ### Setup instructions 1. In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app title and click **Settings** 2. Select the platform (iOS and Android are configured separately) 3. Add your Quick Question postback endpoint to **"Quick Questions data postback URL"** 4. Click **"Test data postback"** to verify the endpoint 5. Click **"Save changes"** **Important:** You must receive a 200 response from the test postback to save the URL. ### Request format Quick Question postbacks use `GET` requests with URL-encoded, nested query parameters and a `sig` signature. Validate them as described in [Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#signature-validation). ### Example payload The following JSON illustrates the payload fields before query-string encoding: ```json { "survey_identifier": "YOUR_SURVEY_IDENTIFIER", "app_name": "YOUR_APP_NAME", "api_token": "YOUR_API_TOKEN", "sdk_version": "3.5.3", "platform": "Android", "placement_tag": "YOUR_PLACEMENT_TAG", "user_identifier": "YOUR_USER_IDENTIFIER", "user_locale": "en", "seen_at": "2025-10-13T22:31:12.455Z", "complete": { "complete_identifier": "tap_qq_68_fb67345f15e08892a0635b7ddcac3138", "completed_at": "2025-10-13T22:31:24.869Z" }, "questions": [ { "question_identifier": "50e2ed203cd2b810ba982b7ce9841939", "question_text": "This is the question", "question_type": "multiple_choice__single", "user_answer": { "value": "Answer F", "identifiers": [ "78bf19eaad5a8bef5c804db7f8760f58" ] } }, { "question_identifier": "e2b3f38f2c5f423ed8303bd68dc2dc77", "question_text": "Test", "question_type": "open_text", "user_answer": { "identifiers": [], "value": "My answer" } } ], "target_audience": [ { "filter_attribute_name": "some_attribute_name", "filter_operator": "equals", "filter_value": "SomeValue", "user_value": "SomeValue" } ] } ``` ## CSV download CSV download is available for any Quick Question through the publisher dashboard. **To download:** 1. Click the **"..."** dropdown in the top right corner of your Quick Question content card 2. Select **"Download CSV"** The CSV file contains all current responses and is updated in real-time. ## API download API download is available for all Quick Questions. **This method is ideal when:** * Quick Question data is not needed immediately * You want to pull all response data on a scheduled basis * You're ingesting data into your own data warehouse **Note:** This data is updated on a daily basis. ### Getting started See the [Reporting API Documentation](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/reporting-api#quick-question-results) for implementation details. To get access to the stats API, contact our account management team at [developers@tapresearch.com](mailto:developers@tapresearch.com). --- # Screen orientation Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/screen-orientation **Critical for landscape apps:** Do not lock screen rotation when displaying TapResearch content. ## Why this matters * **Landscape-locked apps** experience degraded engagement and revenue performance * **Apps allowing portrait rotation** for TapResearch typically achieve **2x the earnings** of landscape-locked apps ## Implementation Allow your app to rotate to portrait mode when displaying TapResearch screens for optimal performance. * **iOS developers:** See [iOS Orientation Handling](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios-alternate-orientation-handling) for how to allow portrait for TapResearch content when the rest of your app is landscape. * **Unity developers:** See the [screen orientation guide](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#screen-orientation-for-landscape-applications) for platform-specific instructions. --- # Handling rewards Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards ## Overview **There are two ways to receive rewards** (choose one per app): * **In-app callbacks** - Default method * **Server-to-server postbacks** - Requires a postback URL. Setting one switches the app from in-app callbacks to postbacks. ### Reward flow ## In-app callbacks In-app callbacks deliver a list of rewards through your SDK's reward callback. **Example:** If a user completes three surveys, the SDK fires one callback containing all three rewards. **Key features:** * TapResearch notifies users of their rewards * Default reward method: used unless you set a postback URL **When callbacks fire:** * When the user closes TapResearch * After successful SDK initialization, to deliver unredeemed rewards :::info Setting a postback URL switches the app from in-app callbacks to server-to-server postbacks. ::: ## Server-to-server postbacks The server-to-server postback is a `GET` request that TapResearch sends to your postback URL as soon as a user earns a reward. The reward details are passed as query parameters, and an HMAC-MD5 signature (`sig`) lets you verify the request came from TapResearch. **Important:** With server-to-server postbacks your app is responsible for notifying users of the rewards they receive — TapResearch does not notify users with this method. ### Set your postback URL In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app's **Settings** tab and select **iOS settings or Android settings**. Enter your URL in **Rewards/monetization postback URL** under **Server-to-server postbacks**. **URL requirements:** * Must begin with `https://` * No spaces or special characters, other than the placeholders described in [Renaming parameters](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#renaming-parameters) * Must respond within 5 seconds. The dashboard test requires HTTP `200`; live postbacks accept any `2xx` response. ### Testing your postback URL Run **Test rewards postback** before saving or updating the URL. The test must return HTTP `200` before the dashboard allows you to save. ### Sample requests ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` **POST requests** To receive postbacks as `POST` requests, contact [developers@tapresearch.com](mailto:developers@tapresearch.com). The fields are sent in a JSON body, with `sech` instead of `sig` for signature validation. ```json { "tid": "777ca23551a4a9173920c22e1ed7f4f3", "api_token": "your-api-token", "cpid": "tap_37939e4ede350f3a8d5149d2fcaa025e", "revenue": "0.5", "payout_amount": 191, "payout_currency": "gold", "uid": "developers@tapresearch.com", "did": "0258DE71-DFEB-4399-BF26-11C1F6E600D2", "payout_type": 3, "offer_identifier": "8f14e45fceea167a5a36dedd4bea2543", "ip_address": "203.0.113.42", "sech": "7cf1c6a66c67c596280437985a76ccea4d78705116b9473be79acf640596a630" } ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | api_token | String | Your app's API token. | | cpid | String | Unique identifier for the reward. Use it to ensure each reward is credited only once. | | did | String | Device identifier, if available. | | ip_address | String | The user's IP address, if available. | | offer_identifier | String | Identifier of the TapResearch offer that issued the reward. | | payout_amount | Integer | Amount of currency earned by this user. | | payout_currency | String | The name of the currency the user earned. | | payout_type | Integer | The action that the user was rewarded for. 0 - Profile Complete, 3 - Survey Rewarded, 9 - Quick Question Completed (SDK only), 10 - Bonus Bar Reward. | | revenue | Decimal | Amount you will be paid for this reward, in USD. | | tid | String | Transaction ID (click ID) of the user's session. Use `cpid`, not `tid`, to identify a reward. | | uid | String | Persistent unique identifier for this user. | | sig | String | `GET` requests only, always the last parameter: an HMAC-MD5 of the query string before it. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#signature-validation). | :::note Postbacks may include additional fields. Validate the signature against the entire query string you receive, not a fixed list of fields. ::: ### Renaming parameters If your postback URL points at a tracking or affiliate network that expects its own parameter names, put placeholders in the URL's query string. TapResearch fills each placeholder with the matching value and sends that value only under your parameter name. | Placeholder | Filled with | |-------------|-------------| | `{UID}` | `uid` | | `{DID}` | `did` | | `{TID}` | `tid` | | `{CPID}` | `cpid` | | `{API_TOKEN}` | `api_token` | | `{PAYOUT_AMOUNT}` | `payout_amount` | | `{PAYOUT_CURRENCY}` | `payout_currency` | | `{REVENUE}` | `revenue` | | `{PAYOUT_TYPE}` | `payout_type` | | `{IP_ADDRESS}` | `ip_address` | **Postback URL you save:** ```text https://your-postback-url.example.com/postback?clickid={TID}&sum={REVENUE}&goal={PAYOUT_TYPE}&custom_field2={PAYOUT_AMOUNT} ``` **Request you receive:** ```text https://your-postback-url.example.com/postback?clickid=777ca23551a4a9173920c22e1ed7f4f3&sum=0.5&goal=3&custom_field2=191&api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_currency=gold&uid=developers%40tapresearch.com&sig=a86c2ce25c178aa375b82e0d3b51e93a ``` **How placeholders work:** * Placeholders are case-sensitive and go in parameter values. * Parameters you don't rename are still sent under their standard names. * Parameters with unknown placeholders, literal braces, or unavailable values are omitted. * Placeholders apply to `GET` postbacks only. `sig` remains the last parameter and covers the entire query string. Don't name a custom parameter `sig`. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#signature-validation). Use **Test rewards postback** to try a URL with placeholders before you save it. ### Security `GET` postbacks are signed with `sig`, as described below. #### Signature validation Every `GET` postback ends with a `sig` parameter: an HMAC-MD5 of the query string that comes before it. Validate it on every request before you reward a user. **Sign the entire query string exactly as you receive it, minus `sig` — don't rebuild it from a fixed list of parameters.** The signature covers every parameter present, including your own static and renamed parameters, so hashing whatever arrives (as the steps and code below do) stays correct as parameters are added. Use your app's **API Secret**, available in its settings in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). The examples use the sample secret `26dcc0fc7b6208fdfeffaf19f627cb4a`. 1. Start with the incoming postback request, for example: ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` 2. Isolate the query string: everything after the `?`. 3. Remove `&sig=` and the signature after it. `sig` is always the last parameter. 4. Percent-decode the rest, preserving literal `+` characters. The examples below use decoders that do this. 5. Run your API secret and the decoded query string through an HMAC-MD5 generator, with lowercase hex output. 6. Compare your result with the `sig` value, using a constant-time comparison. If they match, record the reward and credit the user. ```ruby require "openssl" require "uri" # Sample request URL (in Rails, read the raw query with request.query_string) url = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1" # Isolate the query string and split off sig, which is always last query_string = url.split("?", 2).last signature = query_string[/&sig=([0-9a-f]+)\z/, 1] unsigned_query = query_string.sub(/&sig=[0-9a-f]+\z/, "") # Percent-decode only: "+" stays "+" decoded = URI::Parser.new.unescape(unsigned_query) # Generate HMAC-MD5 api_secret = "26dcc0fc7b6208fdfeffaf19f627cb4a" expected = OpenSSL::HMAC.hexdigest("MD5", api_secret, decoded) puts OpenSSL.secure_compare(expected, signature) # true ``` ```php ``` ```java private static final String API_SECRET = "26dcc0fc7b6208fdfeffaf19f627cb4a"; private static final String ALGO = "HmacMD5"; try { // Sample request URL (in a servlet, read the raw query with request.getQueryString()) String urlString = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1"; // Isolate the query string and split off sig, which is always last String queryString = urlString.substring(urlString.indexOf('?') + 1); int sigIndex = queryString.lastIndexOf("&sig="); String signature = queryString.substring(sigIndex + "&sig=".length()); String unsignedQuery = queryString.substring(0, sigIndex); // Percent-decode only: protect "+" so URLDecoder doesn't turn it into a space String decoded = URLDecoder.decode(unsignedQuery.replace("+", "%2B"), StandardCharsets.UTF_8); // Generate HMAC-MD5 Mac mac = Mac.getInstance(ALGO); mac.init(new SecretKeySpec(API_SECRET.getBytes(StandardCharsets.UTF_8), ALGO)); byte[] bytes = mac.doFinal(decoded.getBytes(StandardCharsets.UTF_8)); StringBuilder expected = new StringBuilder(); for (byte b : bytes) { expected.append(String.format("%02x", b)); } System.out.println(MessageDigest.isEqual( expected.toString().getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))); // true } catch (Exception e) { e.printStackTrace(); } ``` #### IP allowlist To further harden your endpoint, you can restrict incoming postback requests to the following TapResearch source IP addresses: | IP Address | |------------| | 34.198.225.203 | | 3.88.121.21 | | 3.89.214.250 | | 3.92.129.242 | ### Retries If a postback fails, our system retries sending the reward for 48 hours OR until a `2xx` response code is received. --- # TRReward Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward The reward object the SDK returns when a user earns a reward. Your reward callback receives a list of them. The iOS and Android SDKs use slightly different names and types for some properties, shown below. ## Properties | Property | iOS | Android | Description | |---|---|---|---| | Transaction identifier | `transactionIdentifier` (String?) | `transactionIdentifier` (String?) | Unique identifier for the transaction. Use it to deduplicate rewards in your app or in your [server-to-server postback](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#server-to-server-callbacks) handling. | | Placement tag | `placementTag` (String?) | `placementTag` (String?) | The placement tag where the reward was earned. | | Placement identifier | `placementIdentifier` (String?) | `placementIdentifier` (String?) | The placement identifier where the reward was earned. | | Currency name | `currencyName` (String?) | `currencyName` (String?) | Name of the rewarded currency. **Note:** This is the **Currency name** set in the Publisher Dashboard, not the **Display name**. | | Reward amount | `rewardAmount` (Int) | `rewardAmount` (Float?) | Amount of currency rewarded (in your app's currency). | | Payout event | `payoutEvent` (String?) | `payoutEventType` (String?) | Type of payout event — see [Payout event values](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward#payout-event-values). | In Java, use the getters (`getRewardAmount()`, `getPayoutEventType()`, and so on). ## Payout event values | Event | iOS `payoutEvent` | Android `payoutEventType` | |---|---|---| | User completed the profiling flow | `profile_reward` | `PROFILE_REWARD` | | User started a survey but was disqualified | `partial_payout` | `PARTIAL_PAYOUT` | | User successfully completed a survey | `full_payout` | `FULL_PAYOUT` | | Quick Questions payout | `quick_questions_payout` | `QUICK_QUESTIONS_PAYOUT` | --- # SDK error codes Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/sdk-error-codes When using the SDK, you may encounter the following error codes. On Android they arrive as a [`TRError`](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tap-objects) `code`; on iOS as the `NSError` `code`. | Code | Description | |-------|---------------------------------------------------------------------------------| | 1000 | Invalid API token | | 1001 | Missing user identifier | | 1002 | Service unavailable | | 1003 | Missing header | | 1004 | Network timeout | | 1005 | Unknown network error | | 1006 | Javascript execution error | | 1007 | App unavailable | | 1008 | No internet connection | | 2000 | Missing required tag or invalid placement tag | | 2001 | No content available for this placement | | 3000 | Missing attribute | | 3001 | No configuration for landscape orientation while attempting to show in landscape | | 3002 | No configuration for portrait orientation while attempting to show in portrait | | 4000 | Invalid reward identifier | | 6000 | Not initialized | | 6001 | Missing API token | | 6002 | Unable to present UI | | 6003 | Unable to create UI | | 6004 | Unable to decode TRError JSON | | 6005 | Reward delegate was deallocated or not set | | 6006 | Bad or missing custom parameter | | 6007 | Missing application context | | 6008 | Missing placement tag | | 6009 | Missing content callback | | 6010 | SDK not ready | | 6011 | Unable to convert user attributes | | 6012 | Invalid survey Id for placement | | 6013 | No surveys for placement | | 6014 | Quick Questions data delegate was deallocated or not set | | 6015 | Invalid boost tag | | 6016 | Too many requests | | 6017 | Placement details missing | | 10000 | Invalid notification command | | 10001 | Notification missing placementTag while command is showContent | | 10002 | Notification missing scheduledDate and timeDelta, cannot schedule | ## Debugging To assist with debugging, please provide: * The error code you're encountering * A complete stack trace of the error * Any relevant context about when the error occurs Contact developers@tapresearch.com with this information for support. --- # Initialization options Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/initialization-options You can pass user attributes when you initialize the SDK. On Android they go in a `TapInitOptions` object; on iOS they are parameters of `initialize`. ## Configuration options | Option | Description | Required? | |-----------------------------------------|------------------------------------------------------------------------------------------------|-----------| | `userAttributes` | User attributes; see [User attributes and targeting](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes) | No | | `clearPreviousAttributes` | Replaces previously sent attributes with this set (default `false`), see [below](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/initialization-options#clearing-previous-attributes) | No | `initialize` takes an optional `initOptions` parameter of type `TapInitOptions`: ```kotlin data class TapInitOptions( val userAttributes: HashMap? = null, val clearPreviousAttributes: Boolean? = false, ) ``` See [SDK initialization](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#sdk-initialization) for a full example. Pass `userAttributes` and `clearPreviousAttributes` directly to `initialize`: ```swift TapResearch.initialize(withAPIToken: apiToken, userIdentifier: userIdentifier, userAttributes: ["is_vip": 1], clearPreviousAttributes: false, sdkDelegate: self) { (error: NSError?) in // Handle any error } ``` See [Send user attributes](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#send-user-attributes) for Swift and Objective-C examples. ## User attributes in initialization **Best practice:** If user attributes are known at SDK initialization, pass them through initialization instead of using `sendUserAttributes`. **Benefit:** Faster load times for targeted content. ## Clearing previous attributes Attribute names must be strings and must not begin with `tapresearch_`, which is reserved for TapResearch. Use string or numeric values for targeting. For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. Set `clearPreviousAttributes` to `false` to merge new values with existing attributes, or `true` to replace the attributes you previously sent. --- # User attributes and targeting Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes User attributes allow you to store additional information about users as key-value pairs. Use them to track information such as: * Game level * Payer status * Custom user segments * Any other data useful to your organization ## SDK setup This page covers how to set up user attributes in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). **Important:** Attributes only appear in the Publisher Dashboard filters **after they've been sent from the SDK at least once**. For platform-specific implementation details, see: - [Android](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android#send-user-attributes) - [iOS](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios#send-user-attributes) - [Unity](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity#send-user-attributes) - [React Native](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native#send-user-attributes) - [Flutter](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/flutter#sending-user-attributes) Attribute names must be strings and must not begin with `tapresearch_`, which is reserved for TapResearch. Use string or numeric values for targeting. For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. Set `clearPreviousAttributes` to `false` to merge new values with existing attributes, or `true` to replace the attributes you previously sent. ## Targeting based on user attributes You can add up to **3 filters** per piece of content. **Note:** Capping and pacing is only available for currency sale banner content, not Multiple Choice questions. ### Example: Targeting users in France The screenshot shows a partial list of targeting criteria received by TapResearch. Sending different attributes enables you to target different user segments. ### Targeting operators * `is equal to` - Match a specific value, or any value in a list of values * `does not equal` - Exclude a specific value * `is greater/equal to` and `is less/equal to` - Numerical comparisons. For date attributes such as First Seen At, these appear as `is newer than` and `is older than` **Multiple values for one attribute:** Add each value as its own separate entry in the filter — for example, three separate entries for `US`, `CA`, and `GB` when targeting multiple countries, or three separate entries for each tenure level. **Do not** enter them as a single comma-separated string (e.g. `US, CA, GB`) — the filter will treat that as one literal value and it won't match anything. ## Important limitations * **Case sensitivity** - Attributes are case-sensitive: `test` and `Test` are different attributes * **Dates** - Stored as strings: `2020-01-01` and `2020-1-1` are different values * **Booleans** - **Do not use native booleans**. Use stringified values (`"true"`) or integers (`0`, `1`) instead * **Integer vs String** - `1` and `"1"` are different values. For numerical comparisons, send integer values ## Built-in attributes These built-in attributes are also available for targeting: ### Country The user's country location. * Use 2-letter ISO country codes (e.g., `US` for United States) ### First Seen At The first time TapResearch saw this user. * Use ISO8601 date strings: `2020-01-01T00:00:00.000Z` * Use relative dates: `3d` targets users seen more than 3 days ago * Date-only format works: `2020-01-01` equals `2020-01-01T00:00:00.000Z` ### Test Devices Targets only devices in your Test device list. * Use for testing content before going live ### Audience Percentage Targets a percentage of users. * Select groups out of 10 (each group = 10% of users) * Example: Select 3 groups to target 30% of users ## Examples ```kotlin userAttributes = hashMapOf( "testStatus" to "VIP", "testInt" to 2, "testIntString" to "2", "vip_status" to "super-vip", "Some Date" to Instant.now().toString(), // 2023-08-31T10:15:30.00Z // The below will match on relative dates, so you can filter by "a Month Ago" >= 3d "a Month Ago" to LocalDate.now().minus(1, ChronoUnit.MONTHS).toString(), // 2023-07-31 "testCAPSUpper" to "CAPS", // Matches "CAPS" but not "caps" "testCAPSLower" to "caps", // Matches "caps" but not "CAPS" ), ``` ## Technical details ### Attribute caching User attributes are cached for **1 minute** on the backend. * Changes to attributes may take up to **1 minute** to be reflected when querying for content ### Caching examples **Example 1: User Levels Up** Scenario: User sees a placement, levels up, and views the same placement again. ```text playerLevel = 1 changes to playerLevel = 2 ``` You have two pieces of content, one for level 1 and one for level 2. * The user shows content for a placement * The user's level is 1 * The user plays the game and levels up to 2 * The user clicks the placement again and its been less than 1m * The user sees the same content for that placement, not the level 2 content **Example 2: New Attribute Added** Scenario: You have two pieces of content, one for attribute 1 and one for attribute 2. ```text attribute1 = 1 ``` You add an attribute ```text attribute2 = 2 ``` The user has seen a placement, gets an additional attribute they did not have before, and sees new content for that placement * The user shows content for a placement * The user has an additional attribute added to their user attributes that did not exist before * The user clicks the placement again and its been less than 1m * The user sees the _new_ content for that placement, based on the filters for that content **Example 3: Date Targeting** * Relative dates * You want to show content to users who have been playing for 1 month * You'll want to target where `First seen at <= 30d` * Absolute dates * You want to show content to users who have been playing for 1 month * You'll want to target where `First seen at <= 2020-01-01T00:00:00.000Z` **Example 4: Boolean Values** * A user is a VIP * String * Send the attribute `is_vip = "true"` * You'll want to target where `is_vip = "true"` * Integer * Send the attribute `is_vip = 1` * You'll want to target where `is_vip = 1` **Example 5: Multiple Values** * You have multiple user types and want to target some of them * Send the attribute `user_type = "vip"` or `user_type = "normal"` or `user_type = "super-vip"` * Target where `user_type = "vip"` or `user_type = "super-vip"` using the `is equal to` operator — add `vip` and `super-vip` as **two separate entries** in the filter UI, not as one comma-separated value ## Troubleshooting ### Content not matching? **Common issue:** Sending strings instead of integers * Sending `"1"` (string) will not match filters expecting `1` (integer) * **Solution:** Send the value as an integer: `1` --- # Hosted Link Source: https://supply-docs.tapresearch.com/docs/3.x/hosted-link/overview The Hosted Link is the fastest, **low-code** way to connect your users to TapResearch surveys. You add a call-to-action in your app that opens a single URL, and TapResearch handles eligibility, survey selection, and presentation on our side. There's no SDK to install and no API to call. See the [Setup checklist](https://supply-docs.tapresearch.com/docs/3.x/setup) to prepare your account and app. ## How it works You embed one hosted link in your app or web experience. The link contains a **placeholder token** that you swap out per user before sending the user to it — a unique identifier for the user. 1. **Build the link per user.** Take the hosted link from your dashboard and substitute the placeholder token (the user identifier) with that user's real value. 2. **Send the user to the link.** When they tap your call-to-action, open the built link. 3. **TapResearch handles the rest.** Eligibility, targeting, and survey matching all happen on TapResearch's side — there is no eligibility call for you to make. 4. **Rewards arrive via postback.** When a user earns a reward, TapResearch notifies your backend with a server-to-server postback. ## When to use it The Hosted Link trades control for speed — it's the fastest launch, with eligibility and presentation handled for you. Reach for a different path if you need: * **To keep users inside your app** — use the **SDK** path. The Hosted Link opens in the device's browser (see the warning below). * **Native survey tiles or in-app placements** — use the **SDK** path. * **An eligibility check before you send a user** — use the **Eligibility API** path. * **A survey feed you render yourself, or profile / impanel support** — use the **Survey Feed API** path. :::warning Open in a browser, not an in-app WebView The Hosted Link is designed to open in the device's browser. Loading it inside an in-app WebView is **not recommended** — the survey flow has nuances (notably how the close and abandon buttons need to behave) that are difficult to get right in a custom WebView. If you want to keep users inside your app, use the **SDK** instead — it handles the in-app survey experience automatically. ::: ## What you need * The **hosted link** for your placement, from the Publisher Dashboard (**Settings** → **Entry URLs**). * A unique **user identifier** to substitute into the link for each user. No API token or eligibility request is required. The integration is light: add a call-to-action that opens the link, template the `uid` per user, and set up the reward [postback](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards) on your backend. ## Next steps * **[Implementation](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/setup)** — the URL format, the placeholder token to substitute per user, and what happens when a user opens the link. * **[Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards)** — receive reward notifications on your backend with the server-to-server postback. --- # Integrate Hosted Link with AI Source: https://supply-docs.tapresearch.com/docs/3.x/hosted-link/integrate-with-ai Copy the prompt below into your AI assistant — Claude, ChatGPT, Cursor, Codex, or whatever you use. It points the agent at our full, always-current Hosted Link reference, so it works from the real documentation instead of guessing. Hosted Link is the low-code path, so there's not much to build — mostly templating the link per user and handling the reward postback. Your agent can walk you through both. Review what it writes, and reach for the rest of these docs whenever you want the details yourself. ## After you integrate: verify it's working Hosted Link is low-code, so verification is quick. Complete the **Account Setup** checklist on your app's **Integration** tab first (currency, a placement with content, and a test device), then check the flow end-to-end on a **test device**. **Your AI agent can help** run and debug these steps, but it can't see your account — so the **Hosted Link Implementation** checklist on the same tab is what confirms each step actually passed. :::tip Use a real test session Open the link on a real test device — a test postback on its own won't exercise the flow. Add a test device first (**More** menu → **Test devices**). ::: 1. **Reward postback URL configured** — Set your server-to-server postback URL in your app settings. 2. **Enter survey wall from app** — Open your templated Hosted Link on a test device; it should load the survey wall. 3. **Generate test survey complete** — Complete a test survey through the link. 4. **Reward redeemed for test user** — Confirm your endpoint receives a signed `GET` (validate the HMAC `sig`) and credits the reward. When the checklist is complete, use **Go Live** on the same tab. For deeper guidance, see [Testing](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa). --- # Implementation Source: https://supply-docs.tapresearch.com/docs/3.x/hosted-link/setup This page covers how to use your hosted link: the URL format, the value you substitute per user, and what happens when a user opens the link. You need a TapResearch account and a **Hosted Link** app before you begin. [Sign up](https://www.tapresearch.com/suppliers/sign_up) if you don't have an account. ## The hosted link format Your dashboard generates the hosted links for you. In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app's **Settings** tab and select **Entry URLs**: each published placement has its own link. A link points at one placement — identified by your **app title identifier** and the **placement tag** in the path — and carries the user you're sending in the `uid` query parameter: ```text https://www.tapresearch.com/supply_api/v4/app_titles//placements//redirect?uid= ``` There is **no API token** in the link — the Hosted Link requires no authentication. The dashboard fills in the path for you; you only substitute the per-user identifier. ## Values to substitute The `` and `` are already filled in by the dashboard and are fixed per placement. Before you send a user to the link, fill in the user value: | Value | Location | Required | Description | |-------|----------|----------|-------------| | `` | `uid` query param | Yes | A persistent, unique identifier for the user. Swap this per user so rewards are attributed to the right account. Reuse the same value for a given user across sessions. | | `did` | Query param | No | An optional identifier for the user's device. | :::info One link, swapped per user The hosted link is the same for every user except for the per-user `uid`. Template the link once, then substitute the current user's value at the moment you hand them the link. ::: **Requirements:** * Use a stable `uid` for each user — the same value should map to the same user every time. * URL-encode any values that contain special characters. ## What happens when a user opens the link Opening the hosted link hands the user off to the TapResearch-hosted experience — you don't select, render, or redirect to anything yourself: 1. **We check what's available for the user.** Eligibility and targeting are handled entirely on TapResearch's side — there's no eligibility call for you to make. 2. **The user enters the survey experience.** First-time users go through a short profiling flow, then land on the TapResearch **survey wall**, where they browse and take their available surveys. 3. **Otherwise, they see a "no surveys available" message.** If the user isn't currently eligible for anything, they get a friendly, localized message instead. No reward is issued in this case. After a user completes a survey, TapResearch notifies your backend asynchronously — see [Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards). ## Next steps * **[Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards)** — set up the server-to-server postback so you're notified when a user earns a reward. --- # Handling rewards Source: https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards With the Hosted Link path, rewards are delivered to your backend with a **server-to-server postback**. TapResearch sends a request to your postback URL as soon as a user earns a reward, and you verify and credit it on your side. ## Server-to-server postbacks The server-to-server postback is a `GET` request that TapResearch sends to your postback URL as soon as a user earns a reward. The reward details are passed as query parameters, and an HMAC-MD5 signature (`sig`) lets you verify the request came from TapResearch. **Important:** With server-to-server postbacks your app is responsible for notifying users of the rewards they receive — TapResearch does not notify users with this method. ### Set your postback URL In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app's **Settings** tab and select **Hosted Link settings**. Enter your URL in **Rewards/monetization postback URL** under **Server-to-server postbacks**. **URL requirements:** * Must begin with `https://` * No spaces or special characters, other than the placeholders described in [Renaming parameters](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards#renaming-parameters) * Must respond within 5 seconds. The dashboard test requires HTTP `200`; live postbacks accept any `2xx` response. ### Testing your postback URL Run **Test rewards postback** before saving or updating the URL. The test must return HTTP `200` before the dashboard allows you to save. ### Sample requests ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | api_token | String | Your app's API token. | | cpid | String | Unique identifier for the reward. Use it to ensure each reward is credited only once. | | did | String | Device identifier, if available. | | ip_address | String | The user's IP address, if available. | | offer_identifier | String | Identifier of the TapResearch offer that issued the reward. | | payout_amount | Integer | Amount of currency earned by this user. | | payout_currency | String | The name of the currency the user earned. | | payout_type | Integer | The action that the user was rewarded for. 0 - Profile Complete, 3 - Survey Rewarded, 9 - Quick Question Completed (SDK only), 10 - Bonus Bar Reward. | | revenue | Decimal | Amount you will be paid for this reward, in USD. | | tid | String | Transaction ID (click ID) of the user's session. Use `cpid`, not `tid`, to identify a reward. | | uid | String | Persistent unique identifier for this user. | | sig | String | `GET` requests only, always the last parameter: an HMAC-MD5 of the query string before it. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards#signature-validation). | :::note Postbacks may include additional fields. Validate the signature against the entire query string you receive, not a fixed list of fields. ::: ### Renaming parameters If your postback URL points at a tracking or affiliate network that expects its own parameter names, put placeholders in the URL's query string. TapResearch fills each placeholder with the matching value and sends that value only under your parameter name. | Placeholder | Filled with | |-------------|-------------| | `{UID}` | `uid` | | `{DID}` | `did` | | `{TID}` | `tid` | | `{CPID}` | `cpid` | | `{API_TOKEN}` | `api_token` | | `{PAYOUT_AMOUNT}` | `payout_amount` | | `{PAYOUT_CURRENCY}` | `payout_currency` | | `{REVENUE}` | `revenue` | | `{PAYOUT_TYPE}` | `payout_type` | | `{IP_ADDRESS}` | `ip_address` | **Postback URL you save:** ```text https://your-postback-url.example.com/postback?clickid={TID}&sum={REVENUE}&goal={PAYOUT_TYPE}&custom_field2={PAYOUT_AMOUNT} ``` **Request you receive:** ```text https://your-postback-url.example.com/postback?clickid=777ca23551a4a9173920c22e1ed7f4f3&sum=0.5&goal=3&custom_field2=191&api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_currency=gold&uid=developers%40tapresearch.com&sig=a86c2ce25c178aa375b82e0d3b51e93a ``` **How placeholders work:** * Placeholders are case-sensitive and go in parameter values. * Parameters you don't rename are still sent under their standard names. * Parameters with unknown placeholders, literal braces, or unavailable values are omitted. * Placeholders apply to `GET` postbacks only. `sig` remains the last parameter and covers the entire query string. Don't name a custom parameter `sig`. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/hosted-link/handling-rewards#signature-validation). Use **Test rewards postback** to try a URL with placeholders before you save it. ### Security `GET` postbacks are signed with `sig`, as described below. #### Signature validation Every `GET` postback ends with a `sig` parameter: an HMAC-MD5 of the query string that comes before it. Validate it on every request before you reward a user. **Sign the entire query string exactly as you receive it, minus `sig` — don't rebuild it from a fixed list of parameters.** The signature covers every parameter present, including your own static and renamed parameters, so hashing whatever arrives (as the steps and code below do) stays correct as parameters are added. Use your app's **API Secret**, available in its settings in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). The examples use the sample secret `26dcc0fc7b6208fdfeffaf19f627cb4a`. 1. Start with the incoming postback request, for example: ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` 2. Isolate the query string: everything after the `?`. 3. Remove `&sig=` and the signature after it. `sig` is always the last parameter. 4. Percent-decode the rest, preserving literal `+` characters. The examples below use decoders that do this. 5. Run your API secret and the decoded query string through an HMAC-MD5 generator, with lowercase hex output. 6. Compare your result with the `sig` value, using a constant-time comparison. If they match, record the reward and credit the user. ```ruby require "openssl" require "uri" # Sample request URL (in Rails, read the raw query with request.query_string) url = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1" # Isolate the query string and split off sig, which is always last query_string = url.split("?", 2).last signature = query_string[/&sig=([0-9a-f]+)\z/, 1] unsigned_query = query_string.sub(/&sig=[0-9a-f]+\z/, "") # Percent-decode only: "+" stays "+" decoded = URI::Parser.new.unescape(unsigned_query) # Generate HMAC-MD5 api_secret = "26dcc0fc7b6208fdfeffaf19f627cb4a" expected = OpenSSL::HMAC.hexdigest("MD5", api_secret, decoded) puts OpenSSL.secure_compare(expected, signature) # true ``` ```php ``` ```java private static final String API_SECRET = "26dcc0fc7b6208fdfeffaf19f627cb4a"; private static final String ALGO = "HmacMD5"; try { // Sample request URL (in a servlet, read the raw query with request.getQueryString()) String urlString = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1"; // Isolate the query string and split off sig, which is always last String queryString = urlString.substring(urlString.indexOf('?') + 1); int sigIndex = queryString.lastIndexOf("&sig="); String signature = queryString.substring(sigIndex + "&sig=".length()); String unsignedQuery = queryString.substring(0, sigIndex); // Percent-decode only: protect "+" so URLDecoder doesn't turn it into a space String decoded = URLDecoder.decode(unsignedQuery.replace("+", "%2B"), StandardCharsets.UTF_8); // Generate HMAC-MD5 Mac mac = Mac.getInstance(ALGO); mac.init(new SecretKeySpec(API_SECRET.getBytes(StandardCharsets.UTF_8), ALGO)); byte[] bytes = mac.doFinal(decoded.getBytes(StandardCharsets.UTF_8)); StringBuilder expected = new StringBuilder(); for (byte b : bytes) { expected.append(String.format("%02x", b)); } System.out.println(MessageDigest.isEqual( expected.toString().getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))); // true } catch (Exception e) { e.printStackTrace(); } ``` #### IP allowlist To further harden your endpoint, you can restrict incoming postback requests to the following TapResearch source IP addresses: | IP Address | |------------| | 34.198.225.203 | | 3.88.121.21 | | 3.89.214.250 | | 3.92.129.242 | ### Retries If a postback fails, our system retries sending the reward for 48 hours OR until a `2xx` response code is received. --- # Eligibility API Source: https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/overview The **Eligibility API** is a pre-send eligibility check. You make a single server-side call before showing a survey call-to-action, and TapResearch tells you whether the user currently has an offer available. In short: **one call in, `has_offer` true/false out.** Gate your call-to-action on that answer. :::tip Creating your app In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), create a new app and select **Eligibility API**. See the [Setup checklist](https://supply-docs.tapresearch.com/docs/3.x/setup) for the full flow. ::: :::info The Eligibility API returns a **yes/no and a link** — it does **not** return a list of surveys. If you need to render survey tiles or run a profile-first impanel flow, use the [Survey Feed API](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview) instead. ::: ## How it works 1. Before rendering your "Earn rewards" button (or any survey entry point), your backend calls the [eligibility check](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/has-offer). 2. TapResearch responds with `has_offer`: * **`true`** — the user has at least one offer waiting. Show the call-to-action and link the user to the returned `offer_url`. * **`false`** — the user has nothing available right now. Hide or disable the call-to-action so the user never taps into an empty wall. 3. When the user finishes an offer, TapResearch notifies your server through a [postback](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards). ## When to use it Use the Eligibility API when you want to: * Show a survey entry point **only** when there is something for the user to do. * Keep all logic on your server — no SDK, no client-side survey rendering. * Control the exact placement, copy, and styling of the call-to-action yourself. ## What you need * An **API token** for your app (in the Publisher Dashboard, under **Settings** → **Eligibility API settings**). * A stable **`user_identifier`** for each user. ## Next steps * [Eligibility check](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/has-offer) — the request and response in full. * [Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards) — how rewards are reported back to your server. --- # Integrate the Eligibility API with AI Source: https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/integrate-with-ai Copy the prompt below into your AI assistant — Claude, ChatGPT, Cursor, Codex, or whatever you use. It points the agent at our full, always-current Eligibility API reference, so it works from the real documentation instead of guessing. Your agent can implement the server-side eligibility check, the hand-off link, and your postback handler for you. Review what it writes, and reach for the rest of these docs whenever you want the details yourself. ## After you integrate: verify it's working Run through these checks with help from your AI agent — it can write and run them and debug failures, but it can't see your account. So the **API Implementation** checklist on your app's **Integration** tab in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) is what confirms each step actually passed. :::tip Use a real test session These checks register from real activity — a test postback on its own won't complete them. Add a test device first (**More** menu → **Test devices**). ::: 1. **API request received** — Make your first authenticated eligibility request while your app is in test mode. 2. **Reward callback configured** — Set your server-to-server postback URL. 3. **Enter survey wall from app** — For a test user with `has_offer: true`, send the user to the returned `offer_url`. 4. **Generate test survey complete** — Complete a test survey. 5. **Reward redeemed for test user** — Confirm your endpoint receives the signed postback, validates `sig`, and credits the reward. If a step doesn't check off, it's the fastest signal of where the integration broke. When every step is done, use **Go Live** on the same tab. For deeper testing guidance, see [Testing](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa). --- # Eligibility check Source: https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/has-offer Use this endpoint to check whether a user currently has a survey offer available **before** you show them a call-to-action. It returns a simple `has_offer` true/false plus the links you need to send the user into (or away from) the offer. ```text POST https://www.tapresearch.com/supply_api/v4/surveys/offer ``` All requests must be made over **HTTPS**. ## Request Send the parameters as form-encoded body fields, or as a JSON body. | Parameter | Type | Required | Description | | ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------- | | `user_identifier` | String | Yes | Your persistent, unique identifier for this user. | | `api_token` | String | Yes | The API token for your app, found in the Publisher Dashboard. Must belong to an app created with the **Eligibility API** type. | | `client_ip` | String | Yes | The end user's IP address. | | `placement_tag` | String | No | Tag of the survey wall placement to check. | | `attributes_hash` | Object | No | Custom user attributes for [segmentation](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/segmentation). | | `device_identifier` | String | No | The user's device identifier, if you have one. | ### Example request ```bash curl -X POST https://www.tapresearch.com/supply_api/v4/surveys/offer \ -d "user_identifier=user-12345" \ -d "api_token=your-api-token" \ -d "client_ip=203.0.113.42" ``` ## Response The response is a JSON object. `has_offer` is the field to branch on; the URLs and `message_hash` are only returned when `has_offer` is `true`. | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------------------------------------------ | | `has_offer` | Boolean | `true` if the user has an offer available, `false` otherwise. | | `offer_reason` | Integer | A code explaining the result. **Always present.** See [Reason codes](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/has-offer#reason-codes). | | `reason_comment` | String | Human-readable explanation of `offer_reason`. **Always present.** | | `offer_url` | String | The URL to send the user to in order to start the offer. Present when `has_offer` is `true`. | | `abandon_url` | String | The URL to send the user to if they decline or abandon the offer. Present when `has_offer` is `true`. | | `message_hash` | Object | The reward range for this user. Present when `has_offer` is `true`. See [message_hash](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/has-offer#message_hash) below. | ### `message_hash` The reward range to display to the user, in your currency. | Field | Type | Description | | ---------- | ------ | -------------------------------------------- | | `min` | String | Minimum reward amount, in your currency. | | `max` | String | Maximum reward amount, in your currency. | | `currency` | String | The name of the currency the user earns. | ### Example response — offer available ```json { "has_offer": true, "offer_reason": 0, "reason_comment": "Qualified", "offer_url": "https://www.tapresearch.com/router/offers/8f14e45fceea167a5a36dedd4bea2543/pre_entry?sdk=false&uid=user-12345&...", "message_hash": { "min": "10", "max": "700", "currency": "gold" }, "abandon_url": "https://www.tapresearch.com/router/offers/8f14e45fceea167a5a36dedd4bea2543?tid=...&uid=user-12345" } ``` ### Example response — no offer ```json { "has_offer": false, "offer_reason": 19, "reason_comment": "No content available" } ``` ## Using the response * **`has_offer: true`** — Show your call-to-action and link the user to `offer_url`. Use `message_hash` to display the reward range (for example, "Earn 10–700 gold"). If the user backs out, send them to `abandon_url`. * **`has_offer: false`** — Do not show the call-to-action. There is nothing for the user to do right now. When the user completes the offer, TapResearch reports the reward to your server through a [postback](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards). ## Reason codes Every response includes `offer_reason` (an integer) and a matching `reason_comment`. Branch on the code, not the comment text. Common values: | Code | Meaning | | ---- | ------- | | 0 | Qualified — an offer is available (`has_offer: true`) | | 1 | Not qualified — no surveys available for this user right now | | 2 | App is in test mode — only registered test devices are eligible | | 4 | Not a supported country | | 5 | User is under the supported age | | 8 | User is banned | | 19 | No active offers available | | 20 | User identifier is an anonymous device advertising ID, not a real user ID | | 22 | User is blacklisted | | 23 | User doesn't meet the placement's targeting criteria, such as your segmentation attributes | | 24 | No currency is set up for the offer | This is the common subset; other codes may appear. ## Errors Errors return a JSON `error` message with an HTTP status: | Status | Meaning | | ------ | ------- | | `401` | Missing or invalid API token. | | `422` | Invalid or missing request parameters. | | `503` | Temporarily unavailable. | --- # Handling rewards Source: https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards When a user completes an offer surfaced through the Eligibility API, TapResearch notifies your server with a server-to-server postback. Validate every postback before you reward the user. ## Server-to-server postbacks The server-to-server postback is a `GET` request that TapResearch sends to your postback URL as soon as a user earns a reward. The reward details are passed as query parameters, and an HMAC-MD5 signature (`sig`) lets you verify the request came from TapResearch. **Important:** With server-to-server postbacks your app is responsible for notifying users of the rewards they receive — TapResearch does not notify users with this method. ### Set your postback URL In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app's **Settings** tab and select **Eligibility API settings**. Enter your URL in **Rewards/monetization postback URL** under **Server-to-server postbacks**. **URL requirements:** * Must begin with `https://` * No spaces or special characters, other than the placeholders described in [Renaming parameters](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards#renaming-parameters) * Must respond within 5 seconds. The dashboard test requires HTTP `200`; live postbacks accept any `2xx` response. ### Testing your postback URL Run **Test rewards postback** before saving or updating the URL. The test must return HTTP `200` before the dashboard allows you to save. ### Sample requests ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | api_token | String | Your app's API token. | | cpid | String | Unique identifier for the reward. Use it to ensure each reward is credited only once. | | did | String | Device identifier, if available. | | ip_address | String | The user's IP address, if available. | | offer_identifier | String | Identifier of the TapResearch offer that issued the reward. | | payout_amount | Integer | Amount of currency earned by this user. | | payout_currency | String | The name of the currency the user earned. | | payout_type | Integer | The action that the user was rewarded for. 0 - Profile Complete, 3 - Survey Rewarded, 9 - Quick Question Completed (SDK only), 10 - Bonus Bar Reward. | | revenue | Decimal | Amount you will be paid for this reward, in USD. | | tid | String | Transaction ID (click ID) of the user's session. Use `cpid`, not `tid`, to identify a reward. | | uid | String | Persistent unique identifier for this user. | | sig | String | `GET` requests only, always the last parameter: an HMAC-MD5 of the query string before it. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards#signature-validation). | :::note Postbacks may include additional fields. Validate the signature against the entire query string you receive, not a fixed list of fields. ::: ### Renaming parameters If your postback URL points at a tracking or affiliate network that expects its own parameter names, put placeholders in the URL's query string. TapResearch fills each placeholder with the matching value and sends that value only under your parameter name. | Placeholder | Filled with | |-------------|-------------| | `{UID}` | `uid` | | `{DID}` | `did` | | `{TID}` | `tid` | | `{CPID}` | `cpid` | | `{API_TOKEN}` | `api_token` | | `{PAYOUT_AMOUNT}` | `payout_amount` | | `{PAYOUT_CURRENCY}` | `payout_currency` | | `{REVENUE}` | `revenue` | | `{PAYOUT_TYPE}` | `payout_type` | | `{IP_ADDRESS}` | `ip_address` | **Postback URL you save:** ```text https://your-postback-url.example.com/postback?clickid={TID}&sum={REVENUE}&goal={PAYOUT_TYPE}&custom_field2={PAYOUT_AMOUNT} ``` **Request you receive:** ```text https://your-postback-url.example.com/postback?clickid=777ca23551a4a9173920c22e1ed7f4f3&sum=0.5&goal=3&custom_field2=191&api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_currency=gold&uid=developers%40tapresearch.com&sig=a86c2ce25c178aa375b82e0d3b51e93a ``` **How placeholders work:** * Placeholders are case-sensitive and go in parameter values. * Parameters you don't rename are still sent under their standard names. * Parameters with unknown placeholders, literal braces, or unavailable values are omitted. * Placeholders apply to `GET` postbacks only. `sig` remains the last parameter and covers the entire query string. Don't name a custom parameter `sig`. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/eligibility-api/handling-rewards#signature-validation). Use **Test rewards postback** to try a URL with placeholders before you save it. ### Security `GET` postbacks are signed with `sig`, as described below. #### Signature validation Every `GET` postback ends with a `sig` parameter: an HMAC-MD5 of the query string that comes before it. Validate it on every request before you reward a user. **Sign the entire query string exactly as you receive it, minus `sig` — don't rebuild it from a fixed list of parameters.** The signature covers every parameter present, including your own static and renamed parameters, so hashing whatever arrives (as the steps and code below do) stays correct as parameters are added. Use your app's **API Secret**, available in its settings in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). The examples use the sample secret `26dcc0fc7b6208fdfeffaf19f627cb4a`. 1. Start with the incoming postback request, for example: ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` 2. Isolate the query string: everything after the `?`. 3. Remove `&sig=` and the signature after it. `sig` is always the last parameter. 4. Percent-decode the rest, preserving literal `+` characters. The examples below use decoders that do this. 5. Run your API secret and the decoded query string through an HMAC-MD5 generator, with lowercase hex output. 6. Compare your result with the `sig` value, using a constant-time comparison. If they match, record the reward and credit the user. ```ruby require "openssl" require "uri" # Sample request URL (in Rails, read the raw query with request.query_string) url = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1" # Isolate the query string and split off sig, which is always last query_string = url.split("?", 2).last signature = query_string[/&sig=([0-9a-f]+)\z/, 1] unsigned_query = query_string.sub(/&sig=[0-9a-f]+\z/, "") # Percent-decode only: "+" stays "+" decoded = URI::Parser.new.unescape(unsigned_query) # Generate HMAC-MD5 api_secret = "26dcc0fc7b6208fdfeffaf19f627cb4a" expected = OpenSSL::HMAC.hexdigest("MD5", api_secret, decoded) puts OpenSSL.secure_compare(expected, signature) # true ``` ```php ``` ```java private static final String API_SECRET = "26dcc0fc7b6208fdfeffaf19f627cb4a"; private static final String ALGO = "HmacMD5"; try { // Sample request URL (in a servlet, read the raw query with request.getQueryString()) String urlString = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1"; // Isolate the query string and split off sig, which is always last String queryString = urlString.substring(urlString.indexOf('?') + 1); int sigIndex = queryString.lastIndexOf("&sig="); String signature = queryString.substring(sigIndex + "&sig=".length()); String unsignedQuery = queryString.substring(0, sigIndex); // Percent-decode only: protect "+" so URLDecoder doesn't turn it into a space String decoded = URLDecoder.decode(unsignedQuery.replace("+", "%2B"), StandardCharsets.UTF_8); // Generate HMAC-MD5 Mac mac = Mac.getInstance(ALGO); mac.init(new SecretKeySpec(API_SECRET.getBytes(StandardCharsets.UTF_8), ALGO)); byte[] bytes = mac.doFinal(decoded.getBytes(StandardCharsets.UTF_8)); StringBuilder expected = new StringBuilder(); for (byte b : bytes) { expected.append(String.format("%02x", b)); } System.out.println(MessageDigest.isEqual( expected.toString().getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))); // true } catch (Exception e) { e.printStackTrace(); } ``` #### IP allowlist To further harden your endpoint, you can restrict incoming postback requests to the following TapResearch source IP addresses: | IP Address | |------------| | 34.198.225.203 | | 3.88.121.21 | | 3.89.214.250 | | 3.92.129.242 | ### Retries If a postback fails, our system retries sending the reward for 48 hours OR until a `2xx` response code is received. --- # Survey Feed API Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview The **Survey Feed API** is the only TapResearch integration path where you send a participant's profile up front to **impanel** them, then fetch the surveys they qualify for and render those survey tiles inside your own UI. You stay in control of profiling, the survey list, and the participant experience end to end. :::tip Creating your app In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), create a new app and select **Survey Feed API**. See the [Setup checklist](https://supply-docs.tapresearch.com/docs/3.x/setup) for the full flow. ::: :::info Not the Eligibility API The Survey Feed API returns **survey data** for a profiled participant. It is a different product from the **Eligibility API**, which answers a yes/no "does this user have an offer?" check. The two were historically both referred to as the "Supply API" — they are not the same surface. If you only need an eligibility check before sending a user, use the Eligibility API instead. ::: ## How it works 1. **Look up countries & qualifications.** Call `/countries` and `/qualifications` to learn what profile data you can collect. 2. **Impanel the participant.** `POST` their profile to `/players/impanel` so TapResearch can match surveys — or skip this and let TapResearch profile them. 3. **Fetch surveys.** Call `/campaigns` to get the surveys the participant qualifies for. 4. **Render the tiles.** Display the surveys in your own UI and send the participant into one. 5. **Rewards arrive via postback.** When the participant earns a reward, TapResearch notifies your backend with a server-to-server postback. ### Two ways to profile [You pass profile data](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel): Collect profiling data from your users and send it via the Impanel endpoint; TapResearch uses it to match surveys. Uses multiple endpoints — countries & qualifications, impanel, and campaigns. [TapResearch handles profiling](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys): Skip collecting profile data — `GET /campaigns` returns a profile tile that runs TapResearch's built-in profiler, or a list of surveys when pre-profile surveys are on. It's the only required endpoint. ### Pre-profile surveys When TapResearch handles profiling, participants who haven't completed the TapResearch profiler yet can still see real surveys. Instead of returning only the profile tile, `GET /campaigns` returns a list of surveys. **How it works for the participant:** 1. The participant opens any survey tile from the list. 2. They complete the TapResearch profiler. 3. They're redirected into the survey they originally chose — or, if their profiler responses mean they no longer qualify for it, into a different survey tile with similar metrics instead. You don't need to do anything extra — render the tiles and use each survey's `offer_entry_url` as you normally would. * **On by default.** Pre-profile surveys are enabled by default for Survey Feed API apps. To change this for your app, contact your TapResearch account manager. * **Fallback to the profile tile.** If no surveys are available yet for a participant who hasn't been profiled, `/campaigns` returns just the profile tile, as before. * **Shorter lists in some countries.** In some countries, the pre-profile list may be shorter until the participant is profiled. * **After profiling.** Once the participant has completed the profiler, they get the normal survey list. ## When to use it Use the Survey Feed API when you want to: * Render survey tiles **inside your own UI**, fully styled by you. * Send participant profile data up front to target surveys (impanel) — or hand profiling to TapResearch. * Stay in control of the survey list and participant experience end to end. Reach for a different path if you need: * **A simple yes/no eligibility check** before sending a user — use the **Eligibility API**. * **The fastest low-code launch** — use the **Hosted Link**. * **Native mobile placements** — use the **SDK**. ## What you need The base URL for every endpoint in this API: ```text https://api.tapresearch.com/supply_api/v4 ``` * An **API token** for your app, from the Publisher Dashboard (**Settings** → **API settings**). * For `/players/impanel`, a **security hash** (`sech`) generated server-side from your **API secret**. The secret should **never be sent** in any request — use it only to compute the `sech`. See [Authentication](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/authentication#security-hash). * All requests must be made over **HTTPS**. ## Next steps | Endpoint | Method | Description | | ----------------------------------------------------- | ------ | --------------------------------------------------------- | | [`/countries`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-countries) | GET | List supported countries and their IDs. | | [`/qualifications`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-qualifications) | GET | Retrieve qualification questions per country. | | [`/players/impanel`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel) | POST | Register a participant and store qualification responses. | | [`/campaigns`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys) | GET | Fetch available surveys for a qualified participant. | * [Authentication & request signing](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/authentication) — the API token header and the `sech` security hash. * [Redirect URLs](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects) — where participants land after a survey, and how to verify the redirect. After a participant completes a survey, rewards are reported to your backend — see [Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards) for the postback payload and signature validation. ## Support For credentials, redirect configuration, or other questions, contact your TapResearch account manager or email **[developers@tapresearch.com](mailto:developers@tapresearch.com)**. --- # Integrate with AI Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/integrate-with-ai Copy the prompt below into your AI assistant — Claude, ChatGPT, Cursor, Codex, or whatever you use. It points the agent at our full, always-current Survey Feed API reference, so it works from the real documentation instead of guessing. The Survey Feed API is the full-control path — fetching surveys, rendering them, and handling reward postbacks yourself — so there's the most to build here. Your agent can work through it with you. Review what it writes, and reach for the rest of these docs whenever you want the details yourself. ## After you integrate: verify it's working Run through these checks with help from your AI agent — it can write and run them and debug failures, but it can't see your account. So your app's **Integration** tab in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) is what confirms each step actually passed. :::tip Use a real test session These checks register from real activity — a test postback on its own won't complete them. Add a test device first (**More → Test devices**). ::: 1. **API request received** — Make a successful `GET /campaigns` request while your app is in test mode. 2. **Reward callback configured** — Set your server-to-server postback URL. 3. **Enter survey wall from app** — Render a fetched survey and open its `offer_entry_url` from your app so a session starts. 4. **Generate test survey complete** and **Reward redeemed for test user** — Complete a test survey and confirm your postback endpoint receives the signed reward postback and redeems it. If a step doesn't check off, it's the fastest signal of where the integration broke. For deeper testing guidance, see [Testing & QA](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa). --- # Countries & qualifications Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications Before you impanel a participant, fetch the supported countries and the qualification questions for that country. The values returned here are what you send as `country_id` and `profile_data` to the [Impanel](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel) endpoint. ## GET `/countries` List all countries where campaigns are available. ### Parameters | Type | Name | Required | Description | | --- | --- | --- | --- | | Header | `X-Api-Token` | Yes | Your API token. | ### Response Returns an object with a `countries` array. | Field | Description | | --- | --- | | `id` | Internal numeric country ID. | | `name` | Country name. | ```json { "countries": [ { "id": 1, "name": "United States" }, { "id": 2, "name": "Canada" } ] } ``` Use the `id` value when calling [Qualifications](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-qualifications) and [Impanel](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel). ## GET `/qualifications` Retrieve all qualification questions available for a given country. These questions are used to collect profiling data before a participant is impaneled. ### Request #### Headers | Name | Required | Description | | --- | --- | --- | | `X-Api-Token` | Yes | Your API token. | #### Query parameters | Name | Required | Description | | --- | --- | --- | | `country_id` | Yes | Country ID returned from [`/countries`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-countries). | ### Response An object with a `qualifications` array. | Field | Type | Description | | --- | --- | --- | | `question_id` | Integer | Unique question identifier. | | `question_text` | String | Text of the qualification question. | | `country_id` | Integer | The country the question belongs to. | | `name` | String | Short name of the question. | | `answer_type` | String | One of `Single select`, `Multi select`, or `Text`. | | `qualification_answers` | Array | For select types: array of `{ option_text, pre_code }`, where `pre_code` is an integer. Empty for `Text`. | | `required` | Boolean | Whether the answer is mandatory. | #### Example response ```json { "qualifications": [ { "question_id": 50017, "question_text": "When were you born?", "country_id": 1, "name": "Birthday", "answer_type": "Text", "qualification_answers": [], "required": true }, { "question_id": 725, "question_text": "What are your hobbies and interests? Select all that apply.", "country_id": 1, "name": "Hobbies & Interests", "answer_type": "Multi select", "qualification_answers": [ { "option_text": "Arts and Crafts", "pre_code": 1 }, { "option_text": "Boating", "pre_code": 2 }, { "option_text": "Cooking", "pre_code": 3 } ], "required": false } ] } ``` ### Notes - Use the `pre_code` values from `qualification_answers` when sending `profile_data` in the [`/players/impanel`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel) request. - Supported answer types: - **Single select:** user selects one option. - **Multi select:** user can select multiple options. - **Text:** free-form input, such as a ZIP code or a birthdate (`YYYY-MM-DD`). --- **Next step:** [Impanel the participant →](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel) — register them with their qualification responses so TapResearch can match surveys. --- # Impanel / player profile Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel The impanel endpoint ingests a participant's demographic and qualification data. TapResearch's targeting system uses it to determine which surveys the participant is eligible for. Call this endpoint **before** requesting surveys so that [`/campaigns`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys) can return only the campaigns the participant qualifies for. ```http POST https://api.tapresearch.com/supply_api/v4/players/impanel ``` ## Request ### Headers | Name | Required | Description | | --------------------- | -------- | --------------------------------------------------------- | | `X-Api-Token` | Yes | Your API token. | | `X-User-Identifier` | Yes | Your unique, stable participant identifier. | | `X-Device-Identifier` | No | Optional hardware/device ID for multi-device distinction. | ### Body parameters (JSON) | Name | Type | Required | Description | | ----------------- | ------- | -------- | --------------------------------------------------------------------------------- | | `country_id` | Integer | Yes | Country ID from [`/countries`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-countries). | | `profile_data` | Array | Yes | Qualification responses from [`/qualifications`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-qualifications). Must not be empty. | | `ts` | Integer | Yes | Current Unix timestamp (in seconds). | | `sech` | String | Yes | HMAC-SHA256 (hex) of `:`, keyed with your API secret. See [Security hash](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/authentication#security-hash). | | `attributes_hash` | Object | No | Key-value map of custom publisher-defined attributes used for [segmentation](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/segmentation). | ### Profile data format Each object in `profile_data` represents a qualification and its responses. | Field | Type | Description | | ------------- | ------- | ---------------------------------------------------------------------------- | | `question_id` | Integer | ID of the qualification question. | | `values` | Array | Selected `pre_code` integers for select questions, or a single text value for `Text` questions. | ### Segmentation The optional `attributes_hash` field allows publishers to attach custom attributes to a user profile at impanel time. These attributes can be used for **segmentation and targeting** within TapResearch's system. * Keys are defined by the publisher, but can't start with `tapresearch_` (reserved) * Values can be strings or numbers * Attributes are persisted and may influence downstream campaign eligibility #### Example ```json "attributes_hash": { "is_new_user": "true", "subscription_tier": "premium", "days_since_signup": 3 } ``` See [Segmentation](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/segmentation) for how attributes are stored and matched. ### Example request ```json { "country_id": 1, "profile_data": [ { "question_id": 50017, "values": ["1990-04-12"] }, { "question_id": 43, "values": [1] }, { "question_id": 45, "values": ["94568"] } ], "attributes_hash": { "is_new_user": "true", "subscription_tier": "premium" }, "ts": 1713456789, "sech": "a3f9b6c40b31..." } ``` ### Response A successful impanelment returns `200 OK`: ```json { "success": true } ``` Errors return a JSON `error` or `message`. Coded errors also include `error_code`. | Status | Meaning | | ------ | ------- | | `401` | Missing or invalid API token. | | `422` | Missing or invalid request parameters. | | `503` | Temporarily unavailable. | ## Notes * The **API secret must never be included** in the request — only use it server-side to generate the `sech` hash. * Use the `pre_code` values from [`/qualifications`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/countries-qualifications#get-qualifications) for Single select and Multi select data points. * The `sech` is an HMAC-SHA256 hex digest of `user_identifier:ts`, keyed with your API secret. * Both `ts` and `sech` must be provided as JSON fields in the request body. * Call this endpoint **before** retrieving campaigns from [`/campaigns`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys). * A `200 OK` response with `"success": true` confirms the participant has been impaneled successfully. * `attributes_hash` is optional — if omitted, no custom segmentation attributes are applied. --- **Next step:** [Fetch surveys →](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys) — retrieve the campaigns the participant qualifies for. --- # Fetch surveys Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys Retrieve the surveys available to a participant. See [profiling options](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview#two-ways-to-profile). :::info Participants who haven't been profiled yet If TapResearch handles profiling, participants who haven't completed the profiler can still get a list of surveys from this endpoint instead of just the profile tile. See [Pre-profile surveys](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/overview#pre-profile-surveys). ::: ```http GET https://api.tapresearch.com/supply_api/v4/campaigns ``` :::warning Configure your app first Set a post-survey [Redirect URL](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects) and add active content to your placement before requesting surveys. ::: ## Request ### Headers | Name | Required | Description | | --------------------- | -------- | --------------------------------------------------------- | | `X-Api-Token` | Yes | Your API token. | | `X-User-Identifier` | Yes | Your unique, stable participant identifier. | | `X-Device-Identifier` | No | Optional hardware/device ID for multi-device distinction. | ### Query parameters | Name | Required | Description | | --------------- | -------- | --------------------------------------------------------------------------- | | `user_ip` | Yes | Participant IP address (e.g., `107.193.140.76`). | | `device_type` | Yes | Device type — `0` = tablet, `1` = phone, `2` = desktop. | ## Response The response returns a JSON object containing: | Field | Type | Description | | -------------------- | ------ | ---------------------------------------------- | | `surveys` | Array | List of available surveys for the participant. | | `player_eligibility` | Object | Participant eligibility status. | ### Surveys object Each item in the `surveys` array contains: | Field | Type | Description | | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------- | | `identifier` | String | Unique campaign identifier. | | `length_in_minutes` | Integer | Estimated survey duration. | | `conversion_rate` | Integer | Completion rate percentage. | | `reward_amount` | Integer | Reward amount for the participant on a full complete. | | `epc` | String or Integer | Your earnings per click in USD after revenue share, returned as a decimal string or `0`. | | `currency_name` | String | Name of the in-app currency. | | `offer_entry_url` | String | URL to start the survey (redirect the participant here). | | `support_url` | String | TapResearch-hosted support contact URL for this survey. Present this as a "Get Help" link alongside the campaign in your UI. See [Support center](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/support-center). | | `is_hot_tile` | Boolean/Integer | Indicates whether this survey's performance exceeds our internal threshold. | | `category` | String | Survey category label (e.g. `Entertainment`), or `None` when uncategorized. | ### Player eligibility object | Field | Type | Description | | ------------- | ------- | --------------------------------------------------------- | | `is_eligible` | Boolean | Indicates if the participant is eligible to take surveys. | | `reason` | Integer | Eligibility reason code. See [Reason codes](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys#reason-codes). | | `comment` | String | Human-readable eligibility comment. | #### Reason codes When `is_eligible` is `false`, `reason` tells you why, and `surveys` is empty: | Code | Meaning | | ---- | ------- | | 0 | Eligible | | 2 | App is in test mode — only registered test devices are eligible | | 4 | Not a supported country | | 5 | Under the supported age | | 6 | Missing user identifier | | 8 | Banned | | 12 | Missing device/user identifier | | 20 | User identifier is an anonymous device advertising ID, not a real user ID | | 22 | Blacklisted | ### Example response ```json { "surveys": [ { "identifier": "4da2192594d2420648e5996e8bc1cb59", "length_in_minutes": 6, "conversion_rate": 66, "reward_amount": 68, "epc": "1.35", "currency_name": "points", "offer_entry_url": "https://www.tapresearch.com/router/offers/28e0f041675fb74b7ef254482ad1d502/pre_entry?...", "support_url": "https://www.tapresearch.com/support/contact?reference_code=...", "is_hot_tile": 1, "category": "Entertainment" } ], "player_eligibility": { "is_eligible": true, "reason": 0, "comment": "Qualified" } } ``` ## Errors Errors return a JSON `error` or `message`. Coded errors also include `error_code`. | Status | Meaning | | ------ | ------- | | `401` | Missing or invalid API token. | | `422` | Missing or invalid request parameters, or incomplete app setup. | | `503` | Temporarily unavailable. | ## Notes - Call this endpoint after successfully impaneling a participant. See [Impanel](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel). - Use the `offer_entry_url` to redirect participants into a live survey. - The `player_eligibility` block determines whether the user can proceed. If the participant is **not qualified**, the `surveys` array will be empty. - Surveys are already **ordered by our internal yield management algorithm**, so we recommend using them in the order returned. - **Reward amounts** represent what the participant earns **on a full survey complete**. The actual value to you may be lower since TapResearch also pays out for DQs/OQs based on time spent in the survey. - **EPC (earnings per click)** reflects **all payout types**—not just completes—giving you a holistic view of expected earnings. Use this field if you plan to **manage yield on your end** instead of relying solely on reward amount. --- **Next step:** [Handling rewards →](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards) — receive reward postbacks on your backend when a participant completes a survey. --- # Fetch surveys with dynamic currency Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys-with-dynamic-currency ```http GET https://api.tapresearch.com/supply_api/v4/campaigns ``` Retrieve surveys with a per-request currency and exchange rate. This endpoint should be called after impaneling a participant to discover live survey opportunities. :::warning Configure a post-survey redirect URL first `/campaigns` requires your app to have a post-survey redirect URL configured (and an active placement) before it will return surveys. Without one, the call returns `422` with `A valid post-survey redirect URL is required.` — before any surveys are evaluated. See [Redirects](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects). ::: :::info Dashboard currency is still required, but unused A currency variant must still be configured in the [dashboard](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/currencies). It will not be used — `currency_name` and `exchange_rate` below always take precedence. ::: ## Request ### Headers | Name | Required | Description | | --------------------- | -------- | ----------------------------------------------------------- | | `X-Api-Token` | Yes | Your API token. | | `X-User-Identifier` | Yes | Your unique, stable participant identifier. | | `X-Device-Identifier` | No | Optional hardware/device ID for multi-device distinction. | ### Query parameters | Name | Required | Description | | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------| | `user_ip` | Yes | Participant IP address (e.g., `107.193.140.76`). | | `device_type` | Yes | Device type — `0` = tablet, `1` = phone, `2` = desktop. | | `currency_name` | Yes | Display name of the currency for this request (e.g. `Coins`). 191 characters or fewer. | | `exchange_rate` | Yes | Units of your currency per $1.00 USD (e.g. `1000`). Must be a finite decimal in `[10, 100,000,000)`; invalid values return an error. | ## Response The response returns a JSON object containing: | Field | Type | Description | | -------------------- | ------ | ----------------------------------------------- | | `surveys` | Array | List of available surveys for the participant. | | `player_eligibility` | Object | Participant eligibility status. | ### Surveys object Each item in the `surveys` array contains: | Field | Type | Description | | ------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------| | `identifier` | String | Unique campaign identifier. | | `length_in_minutes` | Integer | Estimated survey duration. | | `conversion_rate` | Integer | Completion rate percentage. | | `reward_amount` | Integer | Reward amount for the participant on a full complete. | | `epc` | String or Integer | Your earnings per click in USD after revenue share, returned as a decimal string or `0`. | | `currency_name` | String | Name of the in-app currency. | | `offer_entry_url` | String | URL to start the survey (redirect the participant here). | | `is_hot_tile` | Boolean/Integer | Indicates whether this survey's performance exceeds our internal threshold. | | `support_url` | String | TapResearch-hosted support contact URL for the player's current session. Present this as a "Get Help" link alongside the campaign in your UI. | ### Player eligibility object | Field | Type | Description | | ------------- | ------- | ----------------------------------------------------------- | | `is_eligible` | Boolean | Indicates if the participant is eligible to take surveys. | | `reason` | Integer | Eligibility reason code. See [Reason codes](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys-with-dynamic-currency#reason-codes). | | `comment` | String | Human-readable eligibility comment. | #### Reason codes When `is_eligible` is `false`, `reason` tells you why. An empty `surveys` array almost always traces back to one of these. Common values: | Code | Meaning | | ---- | ------- | | 0 | Eligible | | 2 | App is in test mode — only registered test devices are eligible | | 4 | Not a supported country | | 5 | Under the supported age | | 6 | Missing user identifier | | 8 | Banned | | 12 | Missing device/user identifier | | 20 | User identifier is an anonymous device advertising ID, not a real user ID | | 22 | Blacklisted | This is the common subset; other codes may appear. ### Example response ```json { "surveys": [ { "identifier": "4da2192594d2420648e5996e8bc1cb59", "length_in_minutes": 6, "conversion_rate": 66, "reward_amount": 68, "epc": "1.5", "currency_name": "points", "offer_entry_url": "https://www.tapresearch.com/router/offers/28e0f041675fb74b7ef254482ad1d502/pre_entry...", "is_hot_tile": 1 }, { "identifier": "7e41c40fa02ed36035b5955b58e52388", "length_in_minutes": 15, "conversion_rate": 8, "reward_amount": 232, "epc": "2.0", "currency_name": "points", "offer_entry_url": "https://www.tapresearch.com/router/offers/28e0f041675fb74b7ef254482ad1d502/pre_entry...", "is_hot_tile": 1 } ], "player_eligibility": { "is_eligible": true, "reason": 0, "comment": "Qualified" } } ``` ## Errors Errors return a JSON `error` or `message`. Coded errors also include `error_code`. | Status | Meaning | | ------ | ------- | | `401` | Missing or invalid API token. | | `422` | Missing or invalid request parameters, or incomplete app setup. | | `503` | Temporarily unavailable. | ## Notes - Call this endpoint after successfully impaneling a participant. See [Impanel](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel). - Use the `offer_entry_url` to redirect participants into a live survey. - The `player_eligibility` block determines whether the user can proceed. If the participant is **not qualified**, the `surveys` array will be empty. - Surveys are already **ordered by our internal yield management algorithm**, so we recommend using them in the order returned. - **Reward amounts** represent what the participant earns **on a full survey complete**. The actual value to you may be lower since TapResearch also pays out for DQs/OQs based on time spent in the survey. - **EPC (earnings per click)** reflects **all payout types**—not just completes—giving you a holistic view of expected earnings. Use this field if you plan to **manage yield on your end** instead of relying solely on reward amount. - **Redirects** for these sessions also carry the `currency_name`, signed after `pass_through`; use `{CURRENCY_NAME}` if your [redirect URL](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects) uses placeholders. Include it when you verify `sech`. --- **Next step:** [Handling rewards →](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards) — receive reward notifications on your backend when a participant completes a survey. --- # Handling rewards Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards TapResearch reports every reward to your backend through a server-to-server postback. Because the Survey Feed API has no in-app SDK surface, only the server-to-server postback applies here. ## Server-to-server postbacks The server-to-server postback is a `GET` request that TapResearch sends to your postback URL as soon as a user earns a reward. The reward details are passed as query parameters, and an HMAC-MD5 signature (`sig`) lets you verify the request came from TapResearch. **Important:** With server-to-server postbacks your app is responsible for notifying users of the rewards they receive — TapResearch does not notify users with this method. ### Set your postback URL In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app's **Settings** tab and select **API settings**. Enter your URL in **Rewards/monetization postback URL** under **Server-to-server postbacks**. **URL requirements:** * Must begin with `https://` * No spaces or special characters, other than the placeholders described in [Renaming parameters](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards#renaming-parameters) * Must respond within 5 seconds. The dashboard test requires HTTP `200`; live postbacks accept any `2xx` response. ### Testing your postback URL Run **Test rewards postback** before saving or updating the URL. The test must return HTTP `200` before the dashboard allows you to save. ### Sample requests ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | api_token | String | Your app's API token. | | cpid | String | Unique identifier for the reward. Use it to ensure each reward is credited only once. | | did | String | Device identifier, if available. | | ip_address | String | The user's IP address, if available. | | offer_identifier | String | Identifier of the TapResearch offer that issued the reward. | | payout_amount | Integer | Amount of currency earned by this user. | | payout_currency | String | The name of the currency the user earned. | | payout_type | Integer | The action that the user was rewarded for. 0 - Profile Complete, 3 - Survey Rewarded, 9 - Quick Question Completed (SDK only), 10 - Bonus Bar Reward. | | revenue | Decimal | Amount you will be paid for this reward, in USD. | | tid | String | Transaction ID (click ID) of the user's session. Use `cpid`, not `tid`, to identify a reward. | | uid | String | Persistent unique identifier for this user. | | sig | String | `GET` requests only, always the last parameter: an HMAC-MD5 of the query string before it. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards#signature-validation). | :::note Postbacks may include additional fields. Validate the signature against the entire query string you receive, not a fixed list of fields. ::: ### Renaming parameters If your postback URL points at a tracking or affiliate network that expects its own parameter names, put placeholders in the URL's query string. TapResearch fills each placeholder with the matching value and sends that value only under your parameter name. | Placeholder | Filled with | |-------------|-------------| | `{UID}` | `uid` | | `{DID}` | `did` | | `{TID}` | `tid` | | `{CPID}` | `cpid` | | `{API_TOKEN}` | `api_token` | | `{PAYOUT_AMOUNT}` | `payout_amount` | | `{PAYOUT_CURRENCY}` | `payout_currency` | | `{REVENUE}` | `revenue` | | `{PAYOUT_TYPE}` | `payout_type` | | `{IP_ADDRESS}` | `ip_address` | **Postback URL you save:** ```text https://your-postback-url.example.com/postback?clickid={TID}&sum={REVENUE}&goal={PAYOUT_TYPE}&custom_field2={PAYOUT_AMOUNT} ``` **Request you receive:** ```text https://your-postback-url.example.com/postback?clickid=777ca23551a4a9173920c22e1ed7f4f3&sum=0.5&goal=3&custom_field2=191&api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_currency=gold&uid=developers%40tapresearch.com&sig=a86c2ce25c178aa375b82e0d3b51e93a ``` **How placeholders work:** * Placeholders are case-sensitive and go in parameter values. * Parameters you don't rename are still sent under their standard names. * Parameters with unknown placeholders, literal braces, or unavailable values are omitted. * Placeholders apply to `GET` postbacks only. `sig` remains the last parameter and covers the entire query string. Don't name a custom parameter `sig`. See [Signature validation](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards#signature-validation). Use **Test rewards postback** to try a URL with placeholders before you save it. ### Security `GET` postbacks are signed with `sig`, as described below. #### Signature validation Every `GET` postback ends with a `sig` parameter: an HMAC-MD5 of the query string that comes before it. Validate it on every request before you reward a user. **Sign the entire query string exactly as you receive it, minus `sig` — don't rebuild it from a fixed list of parameters.** The signature covers every parameter present, including your own static and renamed parameters, so hashing whatever arrives (as the steps and code below do) stays correct as parameters are added. Use your app's **API Secret**, available in its settings in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). The examples use the sample secret `26dcc0fc7b6208fdfeffaf19f627cb4a`. 1. Start with the incoming postback request, for example: ```text https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1 ``` 2. Isolate the query string: everything after the `?`. 3. Remove `&sig=` and the signature after it. `sig` is always the last parameter. 4. Percent-decode the rest, preserving literal `+` characters. The examples below use decoders that do this. 5. Run your API secret and the decoded query string through an HMAC-MD5 generator, with lowercase hex output. 6. Compare your result with the `sig` value, using a constant-time comparison. If they match, record the reward and credit the user. ```ruby require "openssl" require "uri" # Sample request URL (in Rails, read the raw query with request.query_string) url = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1" # Isolate the query string and split off sig, which is always last query_string = url.split("?", 2).last signature = query_string[/&sig=([0-9a-f]+)\z/, 1] unsigned_query = query_string.sub(/&sig=[0-9a-f]+\z/, "") # Percent-decode only: "+" stays "+" decoded = URI::Parser.new.unescape(unsigned_query) # Generate HMAC-MD5 api_secret = "26dcc0fc7b6208fdfeffaf19f627cb4a" expected = OpenSSL::HMAC.hexdigest("MD5", api_secret, decoded) puts OpenSSL.secure_compare(expected, signature) # true ``` ```php ``` ```java private static final String API_SECRET = "26dcc0fc7b6208fdfeffaf19f627cb4a"; private static final String ALGO = "HmacMD5"; try { // Sample request URL (in a servlet, read the raw query with request.getQueryString()) String urlString = "https://your-postback-url.example.com/postback?api_token=your-api-token&cpid=tap_37939e4ede350f3a8d5149d2fcaa025e&ip_address=203.0.113.42&offer_identifier=8f14e45fceea167a5a36dedd4bea2543&payout_amount=191&payout_currency=gold&payout_type=3&revenue=0.5&tid=777ca23551a4a9173920c22e1ed7f4f3&uid=developers%40tapresearch.com&sig=435ad87fb971836540ebc3a320efe9f1"; // Isolate the query string and split off sig, which is always last String queryString = urlString.substring(urlString.indexOf('?') + 1); int sigIndex = queryString.lastIndexOf("&sig="); String signature = queryString.substring(sigIndex + "&sig=".length()); String unsignedQuery = queryString.substring(0, sigIndex); // Percent-decode only: protect "+" so URLDecoder doesn't turn it into a space String decoded = URLDecoder.decode(unsignedQuery.replace("+", "%2B"), StandardCharsets.UTF_8); // Generate HMAC-MD5 Mac mac = Mac.getInstance(ALGO); mac.init(new SecretKeySpec(API_SECRET.getBytes(StandardCharsets.UTF_8), ALGO)); byte[] bytes = mac.doFinal(decoded.getBytes(StandardCharsets.UTF_8)); StringBuilder expected = new StringBuilder(); for (byte b : bytes) { expected.append(String.format("%02x", b)); } System.out.println(MessageDigest.isEqual( expected.toString().getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))); // true } catch (Exception e) { e.printStackTrace(); } ``` #### IP allowlist To further harden your endpoint, you can restrict incoming postback requests to the following TapResearch source IP addresses: | IP Address | |------------| | 34.198.225.203 | | 3.88.121.21 | | 3.89.214.250 | | 3.92.129.242 | ### Retries If a postback fails, our system retries sending the reward for 48 hours OR until a `2xx` response code is received. ## Survey outcome parameter For every survey reward postback generated via the Survey Feed API (`payout_type` 3), the `survey_outcome` parameter is included — an integer representing the participant's final status: | Value | Status | Description | | ----- | ------------ | ------------------------------------------------------------------- | | **1** | Complete | The participant successfully completed the survey. | | **2** | Disqualified | The participant was screened out based on targeting requirements. | | **3** | Over Quota | The survey reached its quota while the participant was in progress. | **Postbacks only fire for revenue-generating matches.** If a participant is disqualified or the survey reaches its quota, a postback is sent only when that outcome results in a partial reward (as configured in your App Settings). If no revenue is generated for the event, no postback is triggered. --- # Authentication & request signing Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/authentication The Survey Feed API uses two credentials, both found in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under your app's **Settings** tab → **API settings**: 1. **API token** — identifies your app. Required on every request. 2. **API Secret** — used to compute and verify signatures. Never sent in a request. ## API token Your **API token** is required on **every** Survey Feed API endpoint. Send it in a header: | Header | Description | | ------------- | -------------------------------- | | `X-Api-Token` | Identifies your app. | A missing or invalid token returns `401`. ## User identifiers `/players/impanel` and `/campaigns` also need to know which participant the request is for: | Header | Required | Description | | --------------------- | -------- | ------------------------------------------------ | | `X-User-Identifier` | Yes | Your unique, stable participant identifier. | | `X-Device-Identifier` | Optional | Hardware/device ID for multi-device distinction. | ## Using the API secret Your **API secret** is never transmitted. It's used to compute or verify signatures for: 1. **Impanel security hash** — the `sech` you send to `/players/impanel` ([below](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/authentication#security-hash)) 2. **Redirect signing** — the `sech` on post-survey redirects ([Redirect URLs](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects#how-signing-works)) 3. **Postback signing** — the signature on reward postbacks ([Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards)) ## Security hash The security hash verifies authenticity for `/players/impanel`. ### How to generate 1. Concatenate the `X-User-Identifier` value and the current Unix timestamp with a colon. Example: `123e4567-e89b-12d3-a456-426614174000:1713456789` 2. Compute the HMAC-SHA256 of that string, keyed with your **API secret**. 3. Send the following **as request params** (not headers): * `sech`: the hex-encoded HMAC digest * `ts`: the same Unix timestamp used in the payload 4. Hashes expire after **5 minutes**. #### Ruby example ```ruby require "openssl" api_secret = "26dcc0fc7b6208fdfeffaf19f627cb4a" # example secret user_identifier = "123e4567-e89b-12d3-a456-426614174000" timestamp = Time.now.to_i payload = "#{user_identifier}:#{timestamp}" digest = OpenSSL::HMAC.hexdigest("SHA256", api_secret, payload) ``` --- # Segmentation Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/segmentation Segmentation allows publishers to control which users are eligible for specific survey experiences by attaching **custom attributes** to a participant’s profile. These attributes are sent during impanelment and can be used to **target or restrict access to certain offers**. ## How segmentation works Segmentation is powered by the `attributes_hash` field on the [`/players/impanel`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel) endpoint. When a user is impaneled: 1. You send custom attributes via `attributes_hash` 2. TapResearch stores these attributes on the user profile 3. These attributes are evaluated against your offer targeting rules 4. Only users matching an offer’s targeting rules will qualify ## Sending segmentation data Segmentation attributes are passed as a JSON object: ```json { "attributes_hash": { "is_premium_user": "true", "subscription_tier": "gold", "days_since_signup": 7 } } ``` Attribute names beginning with `tapresearch_` are reserved. ### Supported value types | Type | Example | | ------- | --------------------- | | Number | `1`, `42`, `3.14` | | String | `"gold"`, `"us_west"` | For booleans, send `"true"`/`"false"` or `0`/`1` instead of native booleans. ## Example use cases Use segmentation to target content based on your business logic or user state. ### Examples * Target only new users: ```json { "is_new_user": "true" } ``` * Segment by subscription tier: ```json { "subscription_tier": "premium" } ``` * Limit surveys to highly engaged users: ```json { "days_since_signup": 30 } ``` * Exclude users who have already completed onboarding: ```json { "completed_onboarding": "false" } ``` ## How targeting is applied Configure attribute filters on your offers in the [Publisher Dashboard](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/user-attributes). Users must match all filters to receive an offer. These filters select your content, not individual surveys. ## When to send attributes * Attributes should be sent during [/players/impanel](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel). * Call the endpoint again to update attributes. New values are merged with existing attributes. * Updates take effect immediately for future survey requests ## Best practices * Use clear, consistent naming for keys (e.g. `is_premium_user`, not `premiumFlag`) * Keep attribute values simple and predictable * Avoid sending sensitive or personally identifiable information (PII) * Only send attributes that are relevant for targeting decisions ## Notes * `attributes_hash` is optional * Attributes are stored per user and persist across sessions * Segmentation is evaluated automatically — no additional API calls are required * Attributes determine which of your offers the participant receives --- # Pass-through values Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/pass-through Pass-through values allow publishers to send custom key-value data when sending a participant into a survey. These values are stored with the session and returned as parameters on your **reward postback**, and in the `pass_through` parameter of the [post-survey redirect](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects#parameters). This is useful for passing internal identifiers such as: * Session IDs * Placement IDs * Campaign IDs * User segmentation flags * Any custom reconciliation metadata ## Where to append pass-through values To use pass-through values: 1. Take the survey's `offer_entry_url` from the [`GET /campaigns`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys) response. 2. Append the `pass_through_values` parameter to that URL before redirecting the user. ## Parameter format Append the following parameter to the end of the `offer_entry_url`: ```text &pass_through_values= ``` ### Encoding requirements * The value must be a JSON object (key-value pairs). * Encode the JSON string with **URL-safe Base64**. ## Example ### Original JSON ```json { "session_id": "abc123", "placement_id": "wall_top", "campaign": "spring_promo" } ``` ### Base64 encoded ```text eyJzZXNzaW9uX2lkIjoiYWJjMTIzIiwicGxhY2VtZW50X2lkIjoid2FsbF90b3AiLCJjYW1wYWlnbiI6InNwcmluZ19wcm9tbyJ9 ``` ### Final offer entry URL ```text https://www.tapresearch.com/router/offers/28e0f041675fb74b7ef254482ad1d502/pre_entry?...&pass_through_values=eyJzZXNzaW9uX2lkIjoiYWJjMTIzIiwicGxhY2VtZW50X2lkIjoid2FsbF90b3AiLCJjYW1wYWlnbiI6InNwcmluZ19wcm9tbyJ9 ``` --- ## Postback behavior Each key is returned as a separate parameter on the [reward postback](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/handling-rewards), covered by its signature. Choose names that do not conflict with standard postback fields. ### Example postback parameters For the example above, a GET postback includes these parameters alongside the standard ones: ```text campaign=spring_promo&placement_id=wall_top&session_id=abc123 ``` ## Error handling Send a JSON object encoded as URL-safe Base64. Invalid Base64 can prevent the survey session from starting; decoded data that is not a JSON object prevents reward postback delivery. ## Best practices * Keep the JSON payload small. * Use stable, predictable keys. * Avoid sending sensitive information. * Ensure values are unique per click when needed for reconciliation. * Use the reward postback fields, not pass-through values, to determine rewards. --- --- # Redirect URLs Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects When a participant leaves a survey — complete, disqualification, or any other outcome — TapResearch sends them to your redirect URL with signed parameters. Set it in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under your app's **Settings** tab → **App title settings** → **Redirect URL**. The URL must start with `https://`, and you must run **Test redirect URL** successfully before you can save it. The redirect URL can be handled in **two ways**: 1. **Placeholder substitution** (recommended for full control) 2. **Query parameter appending** (automatic fallback) In both cases, a SHA-256 HMAC signature (`sech`) is added to verify integrity. --- ## Parameters | Name | Description | |-----|-------------| | `status` | Why the participant returned. See [Redirect statuses](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirect-statuses). | | `revenue` | Your revenue in USD, formatted as a float (e.g. `0.45`, `0.0`). | | `reward` | Participant reward in your currency, formatted as a float (e.g. `50.0`). | | `tid` | TapResearch's match ID for the survey attempt — the same value as `cpid` in the reward postback. Blank for session and pre-match statuses. | | `click_id` | TapResearch's identifier for the participant's session. | | `pass_through` | Your [pass-through values](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/pass-through), re-encoded as URL-safe Base64 JSON. Empty if you didn't send any. | | `sech` | SHA-256 HMAC signature verifying the signed parameters. | ## Redirect modes ### Placeholder substitution If your redirect URL contains placeholders using curly braces (e.g., `{STATUS}`), those placeholders will be replaced before redirecting. Supported placeholders: * `{STATUS}` * `{REVENUE}` * `{REWARD}` * `{TID}` * `{CLICK_ID}` * `{PASS_THROUGH}` Use matching lowercase query parameter names, such as `status={STATUS}`, so the values are signed correctly. Example: ```text https://example.com/survey-complete?status={STATUS}&tid={TID}&pass_through={PASS_THROUGH} ``` After substitution: ```text https://example.com/survey-complete?status=1&tid=8f14e45fceea167a5a36dedd4bea2543&pass_through=eyJzZXNzaW9uX2lkIjoiYWJjMTIzIn0%3D&sech=779994bae03770c278f011862e843203ef1db97f1a7a0104cd6f80a951b79395 ``` Only the parameters represented by placeholders are signed. ### Query parameter appending (no placeholders) If your redirect URL does **not** contain placeholders, the system automatically appends all parameters: * `status` * `revenue` * `reward` * `tid` * `click_id` * `pass_through` Example base URL: ```text https://example.com/survey-complete ``` Result: ```text https://example.com/survey-complete?status=1&revenue=0.45&reward=50.0&tid=8f14e45fceea167a5a36dedd4bea2543&click_id=c9f0f895fb98ab9159f51fd0297e236d&pass_through=eyJzZXNzaW9uX2lkIjoiYWJjMTIzIn0%3D&sech=007643e79eff5c8bbb68bbcbbff1f6465325d9570b8349607cac12d617a5813b ``` In this mode, **all parameters are always signed**. ## How signing works The system computes a SHA-256 HMAC using your API secret. ### Signing order Canonical signing order: ```text status,revenue,reward,tid,click_id,pass_through ``` ### Data string construction The signature is created by: 1. Taking only the signed keys (depending on redirect mode). 2. Ordering them according to the canonical signing order above. 3. Joining their URL-decoded values with commas, exactly as they appear in the redirect. Example (the appended redirect above): ```text 1,0.45,50.0,8f14e45fceea167a5a36dedd4bea2543,c9f0f895fb98ab9159f51fd0297e236d,eyJzZXNzaW9uX2lkIjoiYWJjMTIzIn0= ``` Keep blank values as empty strings when joining the signed values. ### Signature generation ```text HMAC-SHA256(data_string, api_secret) ``` The resulting lowercase hex digest becomes the `sech` parameter. ## How to validate the redirect When you receive the redirect: 1. Extract the signed parameters that are present (`status`, `revenue`, `reward`, `tid`, `click_id`, `pass_through`). 2. Join their URL-decoded string values with commas in the canonical order. 3. Compute the SHA-256 HMAC using your API secret. 4. Compare your result to the received `sech`. ```ruby require "openssl" require "uri" api_secret = "26dcc0fc7b6208fdfeffaf19f627cb4a" # example secret url = "https://example.com/survey-complete?status=1&revenue=0.45&reward=50.0&tid=8f14e45fceea167a5a36dedd4bea2543&click_id=c9f0f895fb98ab9159f51fd0297e236d&pass_through=eyJzZXNzaW9uX2lkIjoiYWJjMTIzIn0%3D&sech=007643e79eff5c8bbb68bbcbbff1f6465325d9570b8349607cac12d617a5813b" params = URI.decode_www_form(URI(url).query).to_h signed_keys = %w[status revenue reward tid click_id pass_through] data = signed_keys.select { |key| params.key?(key) }.map { |key| params[key] }.join(",") expected = OpenSSL::HMAC.hexdigest("SHA256", api_secret, data) puts expected == params["sech"] # => true ``` If they do not match: * The parameters were modified, or * The wrong API secret was used. See [Redirect statuses](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirect-statuses) for what each `status` value means. --- # Redirect statuses Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirect-statuses When TapResearch redirects a participant back to your [redirect URL](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/redirects), we include a `status` value that explains **why** the participant returned. The statuses below cover session checks, pre-match checks, post-match outcomes, and profile completion. For **session checks** and **pre-match checks**, redirects return with a **blank `tid`** (match ID) because no reward is issued. **Post-match outcomes always include a `tid`** since most result in a reward and the value supports de-duplication. ## 1. Session checks (immediate returns) These checks run **as soon as the participant loads the entry URL**. If anything looks suspicious — unusual activity patterns, mismatched regions, unsupported devices, or risky behavior — the participant is returned immediately. | Code | Status | Description | | ------ |---------------------------------|---------------------------------------------------------------------------| | **9** | IP Banned | The participant's IP address is banned. | | **19** | ZIP / IP Mismatch | The participant’s ZIP code doesn’t match their detected IP region. | | **20** | Daily Earnings Limit | The participant reached your app's maximum earnings for the past 24 hours. | | **21** | IP Blocked | The participant's IP address is blocked. | | **24** | Unsupported Country | The participant's country is not supported. | | **29** | IP Activity Limit (Completed) | This IP has too many recent completions across surveys. | | **30** | IP Activity Limit (Participant) | This participant’s IP has had too many sessions. | | **31** | Unsupported Device | The participant opened a mobile-only integration on desktop. | | **32** | Modified User Agent | The participant changed their browser/user agent mid-session. | | **33** | Underage | The participant does not meet the minimum age requirement. | | **34** | Quality Blocked | Past behavior indicates quality risks; session blocked. | | **38** | Fraud Blocked | Fraud-like signals detected. | | **40** | Expired Session | The survey session has expired. | | **41** | Invalid Security Hash | Invalid entry URL signature. | | **52** | Location Validation Block | Participant failed location checks (used to detect VPNs/proxies). | ## 2. Pre-match checks (before the survey) These checks happen **after session checks pass** but **before** matching the participant to a live survey. | Code | Status | Description | | ------ | ----------------- | ---------------------------------------------------------------------------------------------------------- | | **5** | Unqualified | The participant did not qualify before entering the survey. | | **6** | Over Quota | The quota closed before the participant entered the survey. | | **8** | IP Over Quota | IP survey quota reached. | | **27** | Entry Term | The participant failed partner-level eligibility checks. *(Only pre-match code that may include a `tid`.)* | ## 3. Post-match outcomes (after matching or survey interaction) These statuses reflect what happened **after** a match attempt or after the participant interacted with the survey. | Code | Status | Description | | ------ | --------------------- | ------------------------------------------------------------------- | | **1** | Complete | The participant completed the survey. | | **2** | Disqualified | The participant was screened out based on targeting requirements. | | **3** | Over Quota | The survey reached its quota while the participant was in progress. | | **4** | Survey Quality Term | Participant failed survey-level quality or consistency checks. | | **10** | System Quality Term | Internal system-level quality checks triggered a termination. | ## 4. Profile completion For partners who rely on TapResearch to collect participant profile data instead of calling `/players/impanel`. This redirect has a blank `tid`. | Code | Status | Description | | ------ | ---------------- | --------------------------------------------------------------------------------------- | | **61** | Profile Complete | The participant finished the TapResearch profile flow and is ready for survey matching. | --- # Support center Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/support-center The `support_url` field returned on each campaign in the [`/campaigns`](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/fetch-surveys) response links to a TapResearch-hosted support contact form for that participant's survey. Use it to give players a direct path to TapResearch support without requiring you to handle survey-related issues yourself. ## When to show it Display a **"Get Help"** or **"Contact Support"** link alongside each campaign in your offerwall UI. The link should point to the `support_url` value for that specific campaign. Show it wherever the player can see the survey — on the survey list, on a detail card, or on a post-redirect screen. The goal is that if a player has a problem with a survey (e.g., completed but didn't receive credit), they have a clear path to report it. ## End-user flow 1. Player clicks your "Get Help" link. 2. TapResearch's support form opens for that participant's survey. 3. Player describes their issue and submits. 4. TapResearch support handles the inquiry directly. Publishers are not in the loop for individual support submissions — TapResearch owns resolution. ## Caveats ### No match or expired session If the survey session can't be found, the support form directs the player to your support team. Provide a fallback contact channel for these cases. ### Don't cache the URL `support_url` is session-specific. Fetch it fresh from the `/campaigns` response on each wall load — do not store and reuse it across sessions. ### Don't construct it manually Always use the value from the API response. The URL format is internal and may change without notice. --- # Best practices Source: https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/best-practices - **Refresh surveys every 2 minutes and whenever a user returns from a survey.** Survey availability changes frequently due to targeting, quotas, and real-time supply/demand. Regularly refreshing ensures users always see fresh, available surveys. Additionally, refresh immediately when a user returns from a survey — completing or exiting a survey can change which surveys they qualify for, so a fresh call ensures the list reflects their current eligibility. - **Keep participant profiles up to date.** Accurate and current profile data directly improves targeting and increases the likelihood of receiving surveys the user qualifies for. [Impanel](https://supply-docs.tapresearch.com/docs/3.x/survey-feed-api/impanel) participants periodically to keep their answers current. - **Map qualifications carefully by country.** Each country has its own qualification set and `pre_code` values. Ensuring correct mapping improves match rates, reduces disqualification rates, and provides a better user experience. - **Use the `epc` field if you plan to manage your own yield.** Surveys are already ordered using TapResearch’s internal yield management algorithm. However, if you choose to override this ordering, rely on the `epc` field rather than `reward_amount`. Note that `reward_amount` reflects **only what the user is paid on a full complete** — TapResearch also pays out for DQs and OQs based on time spent in the survey. `epc` incorporates *all* payout types, giving you a more accurate signal of expected earnings per click if you're doing your own yield management. - **Remove surveys from the UI once a user clicks them.** Most surveys do **not** allow re-entry — if a user taps the same survey again, they’ll be immediately terminated. For surveys that *do* allow multiple entries, they will naturally reappear on the next refresh cycle. --- # Currencies Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/currencies Currencies define how you reward your users. They are managed in the **Settings** tab of the Publisher Dashboard within your App Title. You can create multiple currencies, each with: - A unique name and icon - A configurable exchange rate ## Currency variants Each currency can have multiple **variants**, allowing you to offer different exchange rates to different user cohorts. ## Related Each currency also has an [exchange rate](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/exchange-rate) and an optional [profile reward](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/profile-reward). --- # Exchange rates Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/exchange-rate The **exchange rate** determines how much of your app currency a user receives for $1.00 USD earned. Each [currency variant](https://supply-docs.tapresearch.com/docs/3.x/operational-tools/currencies#currency-variants) has its own rate, set under **Settings → Currency**. The minimum rate is 10. **Why it matters:** setting the right rate is crucial for a successful integration. Rates lower than 25 ($0.04 per unit of virtual currency) significantly reduce survey availability; higher rates unlock access to premium survey experiences. ## Rate implications | Exchange rate | Value of 1 unit | Surveys available | | :-----------: | --------------: | :---------------: | | 1000 | $0.0010 | 100% | | 500 | $0.0020 | 100% | | 250 | $0.0040 | 100% | | 100 | $0.010 | 99% | | 80 | $0.0125 | 95% | | 50 | $0.020 | 85% | | 25 | $0.040 | 65% | | 16 | $0.0625 | 35% | | 10 | $0.10 | 15% | :::tip Boost survey availability Running a [Currency Sale](https://supply-docs.tapresearch.com/docs/3.x/features/currency-sales) temporarily raises your exchange rate for a set period — a quick way to spike survey availability and engagement. ::: --- # Profile reward Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/profile-reward The **profile reward** is a one-time bonus, paid in your currency, that a user earns the first time they **complete their profile** — the short set of demographic and screener questions that determine which surveys they qualify for. Profiling unlocks the survey wall, so the profile reward is a strong incentive to finish it. :::tip Recommended Setting a profile reward typically increases initial engagement and retention. We recommend **10–20% of your exchange rate**. It is a one-time reward for first-time users, and it is publisher-funded (not subsidized by TapResearch). ::: ## Setting it up Configure the profile reward on the **currency**, right next to the exchange rate, under **Settings → Currency** in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). You can set it per currency variant, so different cohorts can receive different profile rewards. Leave it at 0 for no profile reward. ## How it's paid - Paid **once** per user, the first time they complete TapResearch's profiler. - Paid in your virtual currency, in the amount you set. - Delivered through your configured reward callback or postback, like a survey reward. See [TRReward](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward) for the SDK reward fields. --- # Placements Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/placements A **placement** is a configured slot where TapResearch content appears, created under **Manage → Placements** in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview). You create placements no matter which integration you use, then reference each one by its **code tag**. The content-display mechanics on this page (`showContentForPlacement()`, pop-up content) apply to the **SDK** integration. :::info If you make changes to placements, you'll need to reinitialize the SDK to see those changes. ::: There are **2 types of placements:** ### Pop-up placements Use **Pop-up** placements for content that opens automatically when an event occurs in your app (for example, on app start), such as: * Sale banners * Sale interstitials * Quick Questions ### Survey Wall placements Use **Survey wall** placements for content that opens in response to a user action, such as: * Button clicks to open the survey wall * Earn center interactions Both placement types use the `showContentForPlacement()` call, but behave differently based on their type. :::info When a new placement is created, your dev team must implement it in your app code. Once the placement is created, you can add and remove content from it without needing to update your app. ::: ## Pop-up placement content types Pop-up placements support the following content types: * **Sale banner** - A banner promoting a [currency sale](https://supply-docs.tapresearch.com/docs/3.x/features/currency-sales) * **Sale interstitial** - A larger pop-up promoting a currency sale * **Quick question** - A short [Quick Question](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions) for your users ### Behavior When `showContentForPlacement()` is called: 1. The user sees the first piece of content matching their attributes 2. If no attributes are defined, the first content item is shown **Best practice:** Always include a default piece of content for each placement in case users don't match any specific attributes. See the example below: ## Survey Wall placement content types Survey wall placements support: * **Survey wall** - Opens the full survey wall experience when triggered * **Native Survey Tiles** - Returns surveys for you to show as tiles in your own UI (see [Native Survey Tiles](https://supply-docs.tapresearch.com/docs/3.x/features/native-survey-tiles)) ## Creating a placement 1. Click the **Create placement** button in the top right corner of the placements page 2. Fill out the placement details in the modal 3. Use the **Code tag** in your app to display the placement **Example:** `showContentForPlacement("earn-center")` ## Creating content Once you have a placement: 1. Click the **Create content** button in the top right of the page 2. Select your content type from the modal 3. Fill out the form and click **Submit** Your content will immediately go live to users. :::tip Test content on yourself To test specific content for QA, target yourself using user attributes. ::: --- # Reporting API Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/reporting-api To get access to the Reporting API, reach out to our account management team at [developers@tapresearch.com](mailto:developers@tapresearch.com). Every request authenticates with your reporting `access_token`. ## Get supplier stats Hourly stats per app, content item (offer), and country. ### HTTP request ```http GET https://api.tapresearch.com/supply_api/suppliers/stats ``` ### Payload | Parameter | Type | Description | | ------------ | -------- | ------------------------------------------------------------------- | | access_token | String | Unique token used for authorization purposes. | | from | DateTime | Start of the range, in ISO 8601 with a UTC offset, e.g. `2021-04-05T17:00:00-07:00`. | | to | DateTime | End of the range (inclusive), e.g. `2021-04-12T17:00:00-07:00`. | ### Sample request ```bash curl "https://api.tapresearch.com/supply_api/suppliers/stats?access_token=83742aa7dc154c1a84d03213ac1228b5&from=2018-09-20T12:00:00-07:00&to=2018-09-20T13:00:00-07:00" ``` ### Response | Parameter | Type | Description | | -------------------- | ------- | ----------------------------------------------------------- | | datetime | String | The hour that the stats reflect, in the same UTC offset as `from` | | app_name | String | The App name | | id | Integer | The app ID | | app_store_identifier | String | App store identifier | | placement_name | String | The content item's name | | placement_identifier | String | The content item's unique offer identifier | | country_abbr | String | The country abbreviation | | country_name | String | The country name | | impressions | Integer | The number of entries to TapResearch experience | | conversions | Integer | The number of entries with at least one survey payout event | | revenue | String | The revenue in the given hour, as a decimal string (e.g. `"1.32"`) | | platform | String | The app platform name | #### Sample response ```json [ { "datetime": "2018-09-20T12:00:00-07:00", "app_name": "Sample App - iOS", "id": 1234, "platform": "iOS", "app_store_identifier": "com.sample.app", "placement_name": "Main Survey Wall", "placement_identifier": "fb15ebe9c89c3fc4256c33a8e0eabcff", "country_abbr": "US", "country_name": "United States", "impressions": 10, "conversions": 5, "revenue": "1.32" }, { "datetime": "2018-09-20T12:00:00-07:00", "app_name": "Sample App - Android", "id": 1235, "platform": "Android", "app_store_identifier": "com.sample.app", "placement_name": "Main Survey Wall", "placement_identifier": "fb15eabbc89c3fc1466c83a8e0adbbcf", "country_abbr": "US", "country_name": "United States", "impressions": 45, "conversions": 19, "revenue": "3.56" } ] ``` ## Daily stat roll ups Daily revenue per user. ```http GET https://api.tapresearch.com/supply_api/suppliers/daily_stats ``` ### Payload | Parameter | Type | Description | | ------------ | ------ | ------------------------------------------------------------------- | | access_token | String | Unique token used for authorization purposes. | | from | Date | The start date, `YYYY-MM-DD` (e.g. `2021-04-05`). | | to | Date | The end date (inclusive), `YYYY-MM-DD` (e.g. `2021-04-12`). | ### Response | Parameter | Type | Description | | -------------------- | ------- | --------------------------------------------------- | | app_name | String | The App name | | app_store_identifier | String | App store identifier | | country_abbr | String | The country abbreviation | | country_name | String | The country name | | date | Date | The date that the stats reflect, `YYYY-MM-DD` | | revenue | String | The revenue for that user on that day, as a decimal string | | user_identifier | String | The user identifier | | platform | String | The app platform name | | impressions | Integer | The number of entries to TapResearch experience | ### Example response ```json [ { "app_name": "Sample App - iOS", "app_store_identifier": "com.sample.app", "country_abbr": "AU", "country_name": "Australia", "date": "2023-05-13", "revenue": "0.01", "user_identifier": "123456", "platform": "iOS", "impressions": 2 } ] ``` ## Quick Question results ```http GET https://api.tapresearch.com/supply_api/suppliers/quick_question_results ``` ### Payload | Parameter | Type | Description | | ------------ | ------ | --------------------------------------------- | | access_token | String | Unique token used for authorization purposes. | ### Response | Parameter | Type | Description | | ----------- | ------- | -------------------------------------- | | survey_id | Integer | Unique survey ID | | survey_name | String | The Survey Name | | url | String | Url hosting quick question results csv | ### Example response ```json [ { "survey_id": 1, "survey_name": "Users favorite colors", "url": "url_path_to_hosting_response_data.com/results_csv" } ] ``` ## Player summary To get access to this Player Summary API call, reach out to our account management team at [developers@tapresearch.com](mailto:developers@tapresearch.com). We will need publisher contact information for deletion requests. ```http GET https://api.tapresearch.com/supply_api/audience/players/summary.csv ``` ### Payload | Parameter | Type | Description | | ------------ | -------- | -------------------------------------------------------------------- | | access_token | String | Unique token used for authorization purposes. | | from | DateTime | Start of the range, e.g. `2021-04-05T17:00:00-07:00`. | | to | DateTime | End of the range, e.g. `2021-04-12T17:00:00-07:00`. | ### Response CSV containing rows for players that profiled between the from and to dates sent. --- # Testing & going live Source: https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa All apps created in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) start in **test mode**. **In test mode:** * Only registered test devices can access content. * Test devices see a test version of the survey wall, where you generate test survey completes instead of taking real surveys. **Purpose:** Verify the integration is properly configured before going live. :::info Applies to every integration Test mode, test devices, and going live apply to **all** integration paths. The verification walkthrough below uses the SDK survey wall as the example; with the Hosted Link, Eligibility API, or Survey Feed API, you verify through that path's own survey flow. ::: ## Steps to go live 1. [Add a test device](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa#add-a-test-device) 2. [Complete test mode verification](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa#test-mode) 3. [Promote the app to live mode](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa#live-mode) Your app's **Integration** tab tracks these steps in a checklist and holds the **Go Live** button. ## Add a test device Only test devices can access apps in test mode. **To add a test device:** 1. In the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview), open your app and select **Test devices** from the **More** menu in the header. 2. Click **New test device**. 3. Enter: * **Name** — a descriptive name for the device * **Identifier** — use one of: * iOS: [IDFA](https://developer.apple.com/documentation/adsupport/asidentifiermanager) * Android: [Advertising ID](https://developer.android.com/identity/ad-id) * Or the unique user identifier you send to TapResearch 4. Click **Create device**. :::warning The identifier you send must be the one you register Test mode keys off the identifier your integration actually sends — there's no separate "test account." Whatever value you pass as the user identifier (the `userIdentifier` in the SDK, or the `uid` / `user_identifier` in a Hosted Link URL or API request) has to be **registered here as a test device**. If it isn't, that user sees **"no surveys available"** (SDK / Hosted Link) or gets **`has_offer: false` / `is_eligible: false`** (Eligibility API / Survey Feed API) — which looks like a broken integration but is really just test mode doing its job. Firing a test **postback** on its own does **not** exercise this — a real session from a registered test device is what's required. ::: ## Test mode Once the SDK is initialized, open the survey wall from a test device to enter the test view. **Note:** If you're testing from an unsupported country, you'll default to the US and see US surveys. However, you may encounter issues taking surveys due to IP location restrictions. **Complete the test:** Click the **Complete** button to generate a test survey completion. This triggers your configured reward callback or postback. ### QA checklist Once your test device is added: 1. Complete a test survey 2. Complete the remaining steps in your app's **Integration** tab checklist ## Live mode **To go live:** 1. Open your app's **Integration** tab 2. Click **Go Live** ### Testing after going live When your app is live but you're still using a test device: **If you haven't completed the prescreener:** **If you've completed the prescreener:** **Normal live mode (non-test device):** --- # Supported countries and languages Source: https://supply-docs.tapresearch.com/docs/3.x/operational-tools/supported-countries TapResearch supports the following countries and language codes: | Country | Languages Supported | |-----------------|---------------------| | United States | EN, ES | | Canada | EN, FR | | Mexico | ES | | Brazil | PT | | Denmark | DA | | Finland | FI | | France | FR | | Germany | DE | | Italy | IT | | Norway | NB | | Spain | ES | | Sweden | SV | | United Kingdom | EN | | Poland | PL | | Netherlands | NL | | China | ZH-CN | | Hong Kong | ZH-TW | | India | EN, HI | | Japan | JA | | Singapore | EN | | South Korea | KO | | Philippines | EN | | Australia | EN | | New Zealand | EN | | South Africa | EN | --- # Reward reconciliation Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/reward-reconciliation This page covers the additional parameters used for survey reconciliation. For reward postbacks and signature validation, see [Handling rewards](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards). :::info Server-to-server only `status` and `rejection_reason` are only sent through server-to-server postbacks. ::: ## How reconciliation works A rejected reward arrives as a second postback with the same `cpid` and `tid`, a `Rejected` status, and a rejection reason. Handle it as an update to the original reward, not a new reward. ## Parameters | Parameter | Type | Description | |-----------|------|-------------| | status | String | `Pending` or `Rejected`. A reward that isn't rejected is considered settled after 90 days, except in extremely rare circumstances. No additional postback is sent when it settles. | | rejection_reason | String | The reason for rejection, when applicable. | To rename these fields, use `{STATUS}` and `{REJECTION_REASON}` as described in [Renaming parameters](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/handling-rewards#renaming-parameters). --- # FAQ Source: https://supply-docs.tapresearch.com/docs/3.x/faq ## Integration ### My app hasn't gone live yet. Should I wait until it does before I integrate TapResearch? We've had many successful apps integrate before going live. We encourage you to begin the integration process as early as possible. ### How can I be sure that my app is well-integrated with your SDK? We encourage you to send us a test build so we can ensure proper integration between your app, our SDK, and our servers. ### What happened to other SDKs that were available in the past? These legacy integrations have been deprecated and are no longer available: - Adobe AIR SDK - JavaScript SDK - Supplier API (the legacy server API) ## Users and monetization ### What is the difference in monetization between first time users (FTU) and repeat users? All survey takers are valuable, but repeat users offer the largest ROI. The First Time User (FTU) experience is extremely important. We focus heavily on this experience to ensure users see the value of their time and the opportunity to earn rewards. Once an FTU converts to a repeat user, they quickly learn that successfully completing a 3–5 minute survey yields higher reward value than watching 5–10 videos. ### What is a user profile and how is it used to map users to commercial surveys? User profiles are created when users first engage with the survey wall by answering profile questions. Surveys are matched to users based on their profile responses. As users complete more surveys, their LTV (lifetime value) increases, keeping them engaged within your app. ## Payments and reconciliation ### When do we get paid? Payments are processed approximately **90 days** after the month the revenue was earned. To ensure on-time payments, contact [finance@tapresearch.com](mailto:finance@tapresearch.com) to request a payment form. ### What is the reconciliation process and why are user responses rejected? Market researchers may reject user responses for various reasons. The main reasons include: * **VPN/Proxy usage** - Using VPNs or proxies * **Poor quality responses** - Gibberish or nonsensical answers on open-ended questions * **Speeding** - Completing surveys too quickly to have read the questions * **Straightlining** - Selecting answers in a straight line on grid questions * **Inconsistent responses** - Contradictory answers (e.g., claiming to never have heard of Netflix, then saying they pay for it) ### What if I have questions about billing or reconciliation? Please submit any questions to [developers@tapresearch.com](mailto:developers@tapresearch.com). We will do our best to answer in a timely manner. ## Support ### What do we do if a user complains about an issue with a survey? While rare, issues do occur. We empower users to contact us directly through the TapResearch experience, allowing them to report issues so we can reward the user and address any problems. **If you are contacted by a user:** 1. Let us know immediately 2. Email [developers@tapresearch.com](mailto:developers@tapresearch.com) with: * User ID/Player ID/MAID * Time and date of the issue * Screenshots (if available) Our customer support team will respond to and begin investigating all reported incidents within **24 hours**. **Users can submit requests directly from the Survey Wall:** --- # Upgrading to 3.x from 2.x Source: https://supply-docs.tapresearch.com/docs/3.x/upgrading-to-3.x ## What changed * **Banner interstitials and Quick Questions** - New content formats to engage users * **User attributes** - Send attributes in a hash map to target specific content to specific users * **Enhanced error handling** - New callbacks with more specific error codes * **Core SDK logic in JavaScript** - Enables us to push bug fixes and feature updates without requiring SDK updates * **Simplified placement management** - The SDK now manages placement state. Simply ask if content can be shown for a tag, receive a true/false response, and display the content accordingly ## Before you begin ### Account setup 1. **Enable your account** - Contact developers@tapresearch.com to enable your account for 3.x.x SDKs 2. **Create a new app** - Once enabled, create a new 3.x.x-enabled app (you'll need to recreate currencies and placements) 3. **Update credentials** - Update your app to use the new app's API token and placement tags ### Important considerations * **⚠️ Breaking changes:** Version 3.x is not backwards compatible. Code changes are required (outlined below) * **Content requirement:** Add at least one active piece of content to your placements for `canShowContentForPlacement` to return true and for `showContentForPlacement` to show anything * **Early initialization:** Initialize the SDK as early as possible once the user identifier is known. **Do NOT** use anonymous user identifiers, especially when using Quick Questions * **Placement strategy:** Set up placement calls early and throughout your app for survey walls, banners, interstitials, and Quick Questions. This prevents having to add them later ### Testing setup Add your test devices in the [Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) under **More → Test devices**, using either the device identifier or the user identifier you initialize the SDK with: * **Android:** Use the [Android Advertising Identifier](https://developer.android.com/training/articles/ad-id) * **iOS:** Use the [IDFA](https://developer.apple.com/documentation/adsupport/asidentifiermanager) See [Testing](https://supply-docs.tapresearch.com/docs/3.x/testing-and-qa) for more. ### Learn more Not sure what Quick Questions is? Contact developers@tapresearch.com or see [Quick Questions](https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions). **Questions?** Don't hesitate to reach out to developers@tapresearch.com! ## Steps ### Update dependencies Update your TapResearch dependency to 3.x following the instructions for your platform: * [Android](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android) * [iOS](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios) * [Unity](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/unity) * [React Native](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/react-native) ### Update initialization Update your initialization code to include both the **API token** and the **user identifier** of the currently logged-in user. ### Update callbacks **Reward Callback:** * Update to accept an **array of `TRReward` objects** instead of a single object * `TRReward` provides `rewardAmount` and `currencyName` properties * See [additional TRReward information](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward) **Error Callback:** * Create a global error callback to handle errors * **Android:** `TRError` includes `code` and `description` properties * **iOS:** `NSError` includes `code` and `localizedDescription` properties ### Update how you call placements The `showPlacement` method has been renamed: * **Most platforms:** `showContentForPlacement` * **iOS (Swift):** `showContent(forPlacement:)` ---