# 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
```
### Displaying a placement
Placements are locations and moments inside the app 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.
Placement tags can be found in your TapResearch dashboard on the App Settings page.
```typescript
TapSdkAdapter.showContentForPlacement("YOUR_PLACEMENT_TAG");
```
#### Checking that a placement is available
You can check whether a placement is available by using `canShowContentForPlacement()`. It is recommended that you wrap `showContentForPlacement` calls with `canShowContentForPlacement()` to handle situations when a placement may not be available.
```typescript
const placementTag = "YOUR_PLACEMENT_TAG";
TapSdkAdapter.canShowContentForPlacement(placementTag)
.then((canShowPlacement: boolean) => {
console.log(`canShowPlacement is ${canShowPlacement}`);
if (canShowPlacement) {
TapSdkAdapter.showContentForPlacement(placementTag);
}
})
.catch((error: any) => {
console.error(error);
});
```
### 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 via server-to-server postbacks.
:::note
Parameters can only be ASCII characters, Unicode is not supported. You can send a maximum of 5 custom parameters.
:::
```typescript
let customParams: PlacementCustomParamsProps = {
number: 1,
string: 'string',
boolean: true
};
TapSdkAdapter.showContentForPlacement("YOUR_PLACEMENT_TAG", customParams);
```
### Receiving rewards
In the reward callback `onDidReceiveRewards: (rewards: Array) => void;` you will receive an array of `TapSdkReward` objects which contain information about each reward such as amount and currency name:
```typescript
export type TapSdkReward = {
transactionIdentifier: string;
placementTag: string;
placementIdentifier: string;
currencyName: string;
payoutEvent: string;
rewardAmount: number;
};
```
See [TRReward](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward) for a description of each field and its [payout event values](https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/tr-reward#payout-event-values).
### Send user attributes
User attributes let you target content to specific users. Send attributes at initialization or update them after the SDK is ready.
User attributes are passed as a dictionary of key-value pairs.
:::info
If user attributes are known at SDK initialization, it is preferable to pass them as the `userAttributes` argument of `initializeSdk` compared to using `sendUserAttributes`. This will result in quicker load times for targeted content.
:::
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.
```typescript
// const clearPreviousAttributes = false; // combine new attributes with user attributes previously assigned to current user
const clearPreviousAttributes = true; // remove any user attributes previously assigned to current user
const now = new Date();
const userAttributes: TapUserAttributes = {
boolean: 'true',
integer: 1,
double: 1.1,
string: 'string',
isVIP: "true",
now: now.toISOString(),
};
TapSdkAdapter.sendUserAttributes(userAttributes, clearPreviousAttributes);
```
### Update user identifier
You can set the user identifier at any time. This is done by calling `setUniqueUserIdentifier` with a string.
```typescript
TapSdkAdapter.setUniqueUserIdentifier("USER_IDENTIFIER");
```
### 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.
A new callback is called whenever a Quick Question is completed:
```typescript
onDidReceiveQQResponse: (qqResponse: TRQQDataPayload) => void;
```
Pass it to `` as the `onDidReceiveQQResponse` prop:
```tsx
const onQQResponse = useCallback((qqResponse: TRQQDataPayload) => {
console.log('Quick Question answered', qqResponse.survey_identifier);
}, []);
```
This callback will return a **TRQQDataPayload** object (see below). This callback will get called regardless of whether or not a Quick Question server-to-server postback URL
is set on your dashboard (unlike the **onDidReceiveRewards**). This in app callback
allows process and handle the response client side in a way that may otherwise be delayed
with server-to-server postbacks. Ex: If a user likes your application, direct them to
a link to the app store where they can leave a review.
#### Quick Question types
```typescript
export type TRQQDataPayload = {
survey_identifier : string;
app_name : string;
api_token : string;
sdk_version : string;
platform : string;
placement_tag : string;
user_identifier : string;
user_locale : string;
seen_at : string;
questions : [TRQQDataPayloadQuestion];
target_audience? : [TRQQDataPayloadTargetFilter];
complete? : TRQQComplete;
};
export type TRQQComplete = {
complete_identifier : string;
completed_at : string;
};
export type TRQQUserAnswer = {
value : string;
identifiers : [string];
};
export type TRQQDataPayloadQuestion = {
question_identifier : string;
question_text : string;
question_type : string;
rating_scale_size? : number;
user_answer? : TRQQUserAnswer;
};
export type TRQQDataPayloadTargetFilter = {
filter_attribute_name : string;
filter_operator : string;
filter_value : string;
user_value : string;
};
```
### Granting boosts
Call `grantBoost` with a configured [User Boosts](https://supply-docs.tapresearch.com/docs/3.x/features/user-boosts) tag:
```typescript
TapSdkAdapter.grantBoost("YOUR_BOOST_TAG");
```
Receive the result in `onGrantBoostResponse`:
```tsx
{
if (response.success) {
console.log(`Boost granted: ${response.boost_tag}`);
} else {
console.log(`Boost failed: ${response.error?.message}`);
}
}}
/>
```
### Getting placement details
Get placement details by calling:
```typescript
async getPlacementDetails(placementTag: string): Promise
```
It will return `TapSdkPlacementDetails`:
```typescript
export type TapSdkPlacementDetails = {
name: string;
content_type: string;
currency_name: string;
is_sale: boolean;
sale_type: string | null; //'sale' | 'boost'
sale_end_date: string | null;
sale_multiplier: number;
sale_display_name: string | null;
sale_tag: string | null;
bonus_bar_progress: TapSdkBonusBarProgress | null;
};
type TapSdkBonusBarProgress = {
is_active: boolean;
current_completes: number;
bonus_window_end_at: string | null;
bonus_tiers: [TapSdkBonusTier] | null;
error: TapSdkError | null;
};
type TapSdkBonusTier = {
tier_number: number;
completes_needed: number;
reward_amount: number;
status: string | null;
};
```
#### Get placement details example
```tsx
Get Placement Details
```
```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:)`
---